From 5890255b83f3ce9ef9202804dfab744033c0e603 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 3 Nov 2018 15:59:44 +0000 Subject: [PATCH 01/39] cifs: clean up indentation, replace spaces with tab Trivial fix to clean up indentation, replace spaces with tab Signed-off-by: Colin Ian King Signed-off-by: Steve French --- fs/cifs/cifsencrypt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/cifs/cifsencrypt.c b/fs/cifs/cifsencrypt.c index 85b31cfa2f3c..ecdff344e8fe 100644 --- a/fs/cifs/cifsencrypt.c +++ b/fs/cifs/cifsencrypt.c @@ -224,7 +224,7 @@ int cifs_verify_signature(struct smb_rqst *rqst, if (cifs_pdu->Command == SMB_COM_LOCKING_ANDX) { struct smb_com_lock_req *pSMB = (struct smb_com_lock_req *)cifs_pdu; - if (pSMB->LockType & LOCKING_ANDX_OPLOCK_RELEASE) + if (pSMB->LockType & LOCKING_ANDX_OPLOCK_RELEASE) return 0; } From 0967e5457954370cc4e9902bd47b142e9655365f Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Tue, 6 Nov 2018 22:52:43 +1000 Subject: [PATCH 02/39] cifs: use a compound for setting an xattr Improve performance by reducing number of network round trips for set xattr. Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/smb2ops.c | 90 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index e25c7aade98a..6ecf1372a212 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -907,14 +907,28 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, const __u16 ea_value_len, const struct nls_table *nls_codepage, struct cifs_sb_info *cifs_sb) { - int rc; - __le16 *utf16_path; - __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; - struct cifs_open_parms oparms; - struct cifs_fid fid; - struct smb2_file_full_ea_info *ea; + struct cifs_ses *ses = tcon->ses; + struct TCP_Server_Info *server = ses->server; + __le16 *utf16_path = NULL; int ea_name_len = strlen(ea_name); + int flags = 0; int len; + struct smb_rqst rqst[3]; + int resp_buftype[3]; + struct kvec rsp_iov[3]; + struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; + struct cifs_open_parms oparms; + __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; + struct cifs_fid fid; + struct kvec si_iov[SMB2_SET_INFO_IOV_SIZE]; + unsigned int size[1]; + void *data[1]; + struct smb2_file_full_ea_info *ea = NULL; + struct kvec close_iov[1]; + int rc; + + if (smb3_encryption_required(tcon)) + flags |= CIFS_TRANSFORM_REQ; if (ea_name_len > 255) return -EINVAL; @@ -923,6 +937,16 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, if (!utf16_path) return -ENOMEM; + memset(rqst, 0, sizeof(rqst)); + resp_buftype[0] = resp_buftype[1] = resp_buftype[2] = CIFS_NO_BUFFER; + memset(rsp_iov, 0, sizeof(rsp_iov)); + + /* Open */ + memset(&open_iov, 0, sizeof(open_iov)); + rqst[0].rq_iov = open_iov; + rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; + + memset(&oparms, 0, sizeof(oparms)); oparms.tcon = tcon; oparms.desired_access = FILE_WRITE_EA; oparms.disposition = FILE_OPEN; @@ -933,18 +957,22 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, oparms.fid = &fid; oparms.reconnect = false; - rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL, NULL); - kfree(utf16_path); - if (rc) { - cifs_dbg(FYI, "open failed rc=%d\n", rc); - return rc; - } + rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, utf16_path); + if (rc) + goto sea_exit; + smb2_set_next_command(ses->server, &rqst[0]); + + + /* Set Info */ + memset(&si_iov, 0, sizeof(si_iov)); + rqst[1].rq_iov = si_iov; + rqst[1].rq_nvec = 1; len = sizeof(ea) + ea_name_len + ea_value_len + 1; ea = kzalloc(len, GFP_KERNEL); if (ea == NULL) { - SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); - return -ENOMEM; + rc = -ENOMEM; + goto sea_exit; } ea->ea_name_length = ea_name_len; @@ -952,12 +980,36 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, memcpy(ea->ea_data, ea_name, ea_name_len + 1); memcpy(ea->ea_data + ea_name_len + 1, ea_value, ea_value_len); - rc = SMB2_set_ea(xid, tcon, fid.persistent_fid, fid.volatile_fid, ea, - len); + size[0] = len; + data[0] = ea; + + rc = SMB2_set_info_init(tcon, &rqst[1], COMPOUND_FID, + COMPOUND_FID, current->tgid, + FILE_FULL_EA_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + smb2_set_next_command(server, &rqst[1]); + smb2_set_related(&rqst[1]); + + + /* Close */ + memset(&close_iov, 0, sizeof(close_iov)); + rqst[2].rq_iov = close_iov; + rqst[2].rq_nvec = 1; + rc = SMB2_close_init(tcon, &rqst[2], COMPOUND_FID, COMPOUND_FID); + smb2_set_related(&rqst[2]); + + rc = compound_send_recv(xid, ses, flags, 3, rqst, + resp_buftype, rsp_iov); + + sea_exit: kfree(ea); - - SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); - + kfree(utf16_path); + SMB2_open_free(&rqst[0]); + SMB2_set_info_free(&rqst[1]); + SMB2_close_free(&rqst[2]); + free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); + free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); + free_rsp_buf(resp_buftype[2], rsp_iov[2].iov_base); return rc; } #endif From f5942db5ef25531d53f2aa2ff84a46e1763b4196 Mon Sep 17 00:00:00 2001 From: Steve French Date: Wed, 14 Nov 2018 01:37:39 -0600 Subject: [PATCH 03/39] cifs: smb2 commands can not be negative, remove confusing check As Coverity points out le16_to_cpu(midEntry->Command) can not be less than zero. Detected by CoverityScan, CID#1438650 ("Macro compares unsigned to 0") Signed-off-by: Steve French Reviewed-by: Ronnie Sahlberg --- fs/cifs/transport.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c index 83ff0c25710d..5be7302853b6 100644 --- a/fs/cifs/transport.c +++ b/fs/cifs/transport.c @@ -126,9 +126,11 @@ DeleteMidQEntry(struct mid_q_entry *midEntry) if ((slow_rsp_threshold != 0) && time_after(now, midEntry->when_alloc + (slow_rsp_threshold * HZ)) && (midEntry->command != command)) { - /* smb2slowcmd[NUMBER_OF_SMB2_COMMANDS] counts by command */ - if ((le16_to_cpu(midEntry->command) < NUMBER_OF_SMB2_COMMANDS) && - (le16_to_cpu(midEntry->command) >= 0)) + /* + * smb2slowcmd[NUMBER_OF_SMB2_COMMANDS] counts by command + * NB: le16_to_cpu returns unsigned so can not be negative below + */ + if (le16_to_cpu(midEntry->command) < NUMBER_OF_SMB2_COMMANDS) cifs_stats_inc(&midEntry->server->smb2slowcmd[le16_to_cpu(midEntry->command)]); trace_smb3_slow_rsp(le16_to_cpu(midEntry->command), From 97aa495a89a631822431b789b8553b81e3460255 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 15 Nov 2018 00:33:05 -0600 Subject: [PATCH 04/39] cifs: address trivial coverity warning This is not actually a bug but as Coverity points out we shouldn't be doing an "|=" on a value which hasn't been set (although technically it was memset to zero so isn't a bug) and so might as well change "|=" to "=" in this line Detected by CoverityScan, CID#728535 ("Unitialized scalar variable") Signed-off-by: Steve French Reviewed-by: Ronnie Sahlberg --- fs/cifs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index a81a9df997c1..1fe9f3b01703 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -333,7 +333,7 @@ cifs_create_dfs_fattr(struct cifs_fattr *fattr, struct super_block *sb) fattr->cf_mtime = timespec64_trunc(fattr->cf_mtime, sb->s_time_gran); fattr->cf_atime = fattr->cf_ctime = fattr->cf_mtime; fattr->cf_nlink = 2; - fattr->cf_flags |= CIFS_FATTR_DFS_REFERRAL; + fattr->cf_flags = CIFS_FATTR_DFS_REFERRAL; } static int From 07d3b2e4264fb32bf82074311abc20c6de224d61 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 20 Dec 2018 22:03:04 -0600 Subject: [PATCH 05/39] cifs: create a helper function for compound query_info and convert statfs to use it. Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/smb2ops.c | 82 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 6ecf1372a212..daa7115adc5f 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -960,7 +960,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, utf16_path); if (rc) goto sea_exit; - smb2_set_next_command(ses->server, &rqst[0]); + smb2_set_next_command(ses->server, &rqst[0], 0); /* Set Info */ @@ -987,7 +987,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, COMPOUND_FID, current->tgid, FILE_FULL_EA_INFORMATION, SMB2_O_INFO_FILE, 0, data, size); - smb2_set_next_command(server, &rqst[1]); + smb2_set_next_command(server, &rqst[1], 0); smb2_set_related(&rqst[1]); @@ -1837,25 +1837,28 @@ smb2_set_next_command(struct TCP_Server_Info *server, struct smb_rqst *rqst, shdr->NextCommand = cpu_to_le32(len); } +/* + * Passes the query info response back to the caller on success. + * Caller need to free this with free_rsp_buf(). + */ static int -smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon, - struct kstatfs *buf) +smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, + __le16 *utf16_path, u32 desired_access, + u32 class, u32 type, u32 output_len, + struct kvec *rsp, int *buftype) { - struct smb2_query_info_rsp *rsp; - struct smb2_fs_full_size_info *info = NULL; + struct cifs_ses *ses = tcon->ses; + struct TCP_Server_Info *server = ses->server; + int flags = 0; struct smb_rqst rqst[3]; int resp_buftype[3]; struct kvec rsp_iov[3]; struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; struct kvec qi_iov[1]; struct kvec close_iov[1]; - struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = ses->server; - __le16 srch_path = 0; /* Null - open root of share */ u8 oplock = SMB2_OPLOCK_LEVEL_NONE; struct cifs_open_parms oparms; struct cifs_fid fid; - int flags = 0; int rc; if (smb3_encryption_required(tcon)) @@ -1870,15 +1873,15 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon, rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; oparms.tcon = tcon; - oparms.desired_access = FILE_READ_ATTRIBUTES; + oparms.desired_access = desired_access; oparms.disposition = FILE_OPEN; oparms.create_options = 0; oparms.fid = &fid; oparms.reconnect = false; - rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, &srch_path); + rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, utf16_path); if (rc) - goto qfs_exit; + goto qic_exit; smb2_set_next_command(server, &rqst[0], 0); memset(&qi_iov, 0, sizeof(qi_iov)); @@ -1886,12 +1889,11 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon, rqst[1].rq_nvec = 1; rc = SMB2_query_info_init(tcon, &rqst[1], COMPOUND_FID, COMPOUND_FID, - FS_FULL_SIZE_INFORMATION, - SMB2_O_INFO_FILESYSTEM, 0, - sizeof(struct smb2_fs_full_size_info), 0, + class, type, 0, + output_len, 0, NULL); if (rc) - goto qfs_exit; + goto qic_exit; smb2_set_next_command(server, &rqst[1], 0); smb2_set_related(&rqst[1]); @@ -1901,32 +1903,60 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon, rc = SMB2_close_init(tcon, &rqst[2], COMPOUND_FID, COMPOUND_FID); if (rc) - goto qfs_exit; + goto qic_exit; smb2_set_related(&rqst[2]); rc = compound_send_recv(xid, ses, flags, 3, rqst, resp_buftype, rsp_iov); + if (rc) + goto qic_exit; + + *rsp = rsp_iov[1]; + *buftype = resp_buftype[1]; + + qic_exit: + SMB2_open_free(&rqst[0]); + SMB2_query_info_free(&rqst[1]); + SMB2_close_free(&rqst[2]); + free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); + free_rsp_buf(resp_buftype[2], rsp_iov[2].iov_base); + return rc; +} + +static int +smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon, + struct kstatfs *buf) +{ + struct smb2_query_info_rsp *rsp; + struct smb2_fs_full_size_info *info = NULL; + __le16 utf16_path = 0; /* Null - open root of share */ + struct kvec rsp_iov = {NULL, 0}; + int buftype = CIFS_NO_BUFFER; + int rc; + + + rc = smb2_query_info_compound(xid, tcon, &utf16_path, + FILE_READ_ATTRIBUTES, + FS_FULL_SIZE_INFORMATION, + SMB2_O_INFO_FILESYSTEM, + sizeof(struct smb2_fs_full_size_info), + &rsp_iov, &buftype); if (rc) goto qfs_exit; - rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base; + rsp = (struct smb2_query_info_rsp *)rsp_iov.iov_base; buf->f_type = SMB2_MAGIC_NUMBER; info = (struct smb2_fs_full_size_info *)( le16_to_cpu(rsp->OutputBufferOffset) + (char *)rsp); rc = smb2_validate_iov(le16_to_cpu(rsp->OutputBufferOffset), le32_to_cpu(rsp->OutputBufferLength), - &rsp_iov[1], + &rsp_iov, sizeof(struct smb2_fs_full_size_info)); if (!rc) smb2_copy_fs_info_to_kstatfs(info, buf); qfs_exit: - SMB2_open_free(&rqst[0]); - SMB2_query_info_free(&rqst[1]); - SMB2_close_free(&rqst[2]); - free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); - free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); - free_rsp_buf(resp_buftype[2], rsp_iov[2].iov_base); + free_rsp_buf(buftype, rsp_iov.iov_base); return rc; } From 4a3b38aec5778f8eb37b2eb4701baffb1da4ccd5 Mon Sep 17 00:00:00 2001 From: Kenneth D'souza Date: Sat, 17 Nov 2018 10:33:30 +0530 Subject: [PATCH 06/39] Add vers=3.0.2 as a valid option for SMBv3.0.2 Technically 3.02 is not the dialect name although that is more familiar to many, so we should also accept the official dialect name (3.0.2 vs. 3.02) in vers= Signed-off-by: Kenneth D'souza Signed-off-by: Steve French --- fs/cifs/cifsglob.h | 1 + fs/cifs/connect.c | 1 + 2 files changed, 2 insertions(+) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 38ab0fca49e1..7b3b9313dcde 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1790,6 +1790,7 @@ extern struct smb_version_values smb3any_values; extern struct smb_version_operations smb30_operations; extern struct smb_version_values smb30_values; #define SMB302_VERSION_STRING "3.02" +#define ALT_SMB302_VERSION_STRING "3.0.2" /*extern struct smb_version_operations smb302_operations;*/ /* not needed yet */ extern struct smb_version_values smb302_values; #define SMB311_VERSION_STRING "3.1.1" diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 6f24f129a751..f0be8ed6871f 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -304,6 +304,7 @@ static const match_table_t cifs_smb_version_tokens = { { Smb_21, SMB21_VERSION_STRING }, { Smb_30, SMB30_VERSION_STRING }, { Smb_302, SMB302_VERSION_STRING }, + { Smb_302, ALT_SMB302_VERSION_STRING }, { Smb_311, SMB311_VERSION_STRING }, { Smb_311, ALT_SMB311_VERSION_STRING }, { Smb_3any, SMB3ANY_VERSION_STRING }, From f9793b6fcc8ede4c2eb5f2d5816b1c8b4f927032 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Tue, 27 Nov 2018 09:52:04 +1000 Subject: [PATCH 07/39] cifs: change smb2_query_eas to use the compound query-info helper Reducing the number of network roundtrips improves the performance of query xattrs Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/smb2ops.c | 105 ++++++++++++++++++-------------------------- fs/cifs/smb2pdu.c | 12 ----- fs/cifs/smb2pdu.h | 1 - fs/cifs/smb2proto.h | 10 +++-- 4 files changed, 49 insertions(+), 79 deletions(-) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index daa7115adc5f..6c99a146fcec 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -831,72 +831,48 @@ smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon, { int rc; __le16 *utf16_path; - __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; - struct cifs_open_parms oparms; - struct cifs_fid fid; - struct smb2_file_full_ea_info *smb2_data; - int ea_buf_size = SMB2_MIN_EA_BUF; + struct kvec rsp_iov = {NULL, 0}; + int buftype = CIFS_NO_BUFFER; + struct smb2_query_info_rsp *rsp; + struct smb2_file_full_ea_info *info = NULL; utf16_path = cifs_convert_path_to_utf16(path, cifs_sb); if (!utf16_path) return -ENOMEM; - oparms.tcon = tcon; - oparms.desired_access = FILE_READ_EA; - oparms.disposition = FILE_OPEN; - if (backup_cred(cifs_sb)) - oparms.create_options = CREATE_OPEN_BACKUP_INTENT; - else - oparms.create_options = 0; - oparms.fid = &fid; - oparms.reconnect = false; - - rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL, NULL); - kfree(utf16_path); + rc = smb2_query_info_compound(xid, tcon, utf16_path, + FILE_READ_EA, + FILE_FULL_EA_INFORMATION, + SMB2_O_INFO_FILE, + SMB2_MAX_EA_BUF, + &rsp_iov, &buftype, cifs_sb); if (rc) { - cifs_dbg(FYI, "open failed rc=%d\n", rc); - return rc; + /* + * If ea_name is NULL (listxattr) and there are no EAs, + * return 0 as it's not an error. Otherwise, the specified + * ea_name was not found. + */ + if (!ea_name && rc == -ENODATA) + rc = 0; + goto qeas_exit; } - while (1) { - smb2_data = kzalloc(ea_buf_size, GFP_KERNEL); - if (smb2_data == NULL) { - SMB2_close(xid, tcon, fid.persistent_fid, - fid.volatile_fid); - return -ENOMEM; - } + rsp = (struct smb2_query_info_rsp *)rsp_iov.iov_base; + rc = smb2_validate_iov(le16_to_cpu(rsp->OutputBufferOffset), + le32_to_cpu(rsp->OutputBufferLength), + &rsp_iov, + sizeof(struct smb2_file_full_ea_info)); + if (rc) + goto qeas_exit; - rc = SMB2_query_eas(xid, tcon, fid.persistent_fid, - fid.volatile_fid, - ea_buf_size, smb2_data); + info = (struct smb2_file_full_ea_info *)( + le16_to_cpu(rsp->OutputBufferOffset) + (char *)rsp); + rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, + le32_to_cpu(rsp->OutputBufferLength), ea_name); - if (rc != -E2BIG) - break; - - kfree(smb2_data); - ea_buf_size <<= 1; - - if (ea_buf_size > SMB2_MAX_EA_BUF) { - cifs_dbg(VFS, "EA size is too large\n"); - SMB2_close(xid, tcon, fid.persistent_fid, - fid.volatile_fid); - return -ENOMEM; - } - } - - SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); - - /* - * If ea_name is NULL (listxattr) and there are no EAs, return 0 as it's - * not an error. Otherwise, the specified ea_name was not found. - */ - if (!rc) - rc = move_smb2_ea_to_cifs(ea_data, buf_size, smb2_data, - SMB2_MAX_EA_BUF, ea_name); - else if (!ea_name && rc == -ENODATA) - rc = 0; - - kfree(smb2_data); + qeas_exit: + kfree(utf16_path); + free_rsp_buf(buftype, rsp_iov.iov_base); return rc; } @@ -1841,11 +1817,12 @@ smb2_set_next_command(struct TCP_Server_Info *server, struct smb_rqst *rqst, * Passes the query info response back to the caller on success. * Caller need to free this with free_rsp_buf(). */ -static int +int smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, __le16 *utf16_path, u32 desired_access, u32 class, u32 type, u32 output_len, - struct kvec *rsp, int *buftype) + struct kvec *rsp, int *buftype, + struct cifs_sb_info *cifs_sb) { struct cifs_ses *ses = tcon->ses; struct TCP_Server_Info *server = ses->server; @@ -1875,7 +1852,10 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, oparms.tcon = tcon; oparms.desired_access = desired_access; oparms.disposition = FILE_OPEN; - oparms.create_options = 0; + if (cifs_sb && backup_cred(cifs_sb)) + oparms.create_options = CREATE_OPEN_BACKUP_INTENT; + else + oparms.create_options = 0; oparms.fid = &fid; oparms.reconnect = false; @@ -1908,9 +1888,10 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, rc = compound_send_recv(xid, ses, flags, 3, rqst, resp_buftype, rsp_iov); - if (rc) + if (rc) { + free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); goto qic_exit; - + } *rsp = rsp_iov[1]; *buftype = resp_buftype[1]; @@ -1940,7 +1921,7 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon, FS_FULL_SIZE_INFORMATION, SMB2_O_INFO_FILESYSTEM, sizeof(struct smb2_fs_full_size_info), - &rsp_iov, &buftype); + &rsp_iov, &buftype, NULL); if (rc) goto qfs_exit; diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 27f86537a5d1..448031898dd4 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -2768,18 +2768,6 @@ qinf_exit: return rc; } -int SMB2_query_eas(const unsigned int xid, struct cifs_tcon *tcon, - u64 persistent_fid, u64 volatile_fid, - int ea_buf_size, struct smb2_file_full_ea_info *data) -{ - return query_info(xid, tcon, persistent_fid, volatile_fid, - FILE_FULL_EA_INFORMATION, SMB2_O_INFO_FILE, 0, - ea_buf_size, - sizeof(struct smb2_file_full_ea_info), - (void **)&data, - NULL); -} - int SMB2_query_info(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, u64 volatile_fid, struct smb2_file_all_info *data) { diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h index 5671d5ee7f58..05dea6750c33 100644 --- a/fs/cifs/smb2pdu.h +++ b/fs/cifs/smb2pdu.h @@ -1398,7 +1398,6 @@ struct smb2_file_link_info { /* encoding of request for level 11 */ char FileName[0]; /* Name to be assigned to new link */ } __packed; /* level 11 Set */ -#define SMB2_MIN_EA_BUF 2048 #define SMB2_MAX_EA_BUF 65536 struct smb2_file_full_ea_info { /* encoding of response for level 15 */ diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h index 2fe78acd7d0c..4029ee037ab4 100644 --- a/fs/cifs/smb2proto.h +++ b/fs/cifs/smb2proto.h @@ -154,10 +154,6 @@ extern int SMB2_close_init(struct cifs_tcon *tcon, struct smb_rqst *rqst, extern void SMB2_close_free(struct smb_rqst *rqst); extern int SMB2_flush(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_file_id, u64 volatile_file_id); -extern int SMB2_query_eas(const unsigned int xid, struct cifs_tcon *tcon, - u64 persistent_file_id, u64 volatile_file_id, - int ea_buf_size, - struct smb2_file_full_ea_info *data); extern int SMB2_query_info(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_file_id, u64 volatile_file_id, struct smb2_file_all_info *data); @@ -241,4 +237,10 @@ extern void smb2_copy_fs_info_to_kstatfs( extern int smb311_crypto_shash_allocate(struct TCP_Server_Info *server); extern int smb311_update_preauth_hash(struct cifs_ses *ses, struct kvec *iov, int nvec); +extern int smb2_query_info_compound(const unsigned int xid, + struct cifs_tcon *tcon, + __le16 *utf16_path, u32 desired_access, + u32 class, u32 type, u32 output_len, + struct kvec *rsp, int *buftype, + struct cifs_sb_info *cifs_sb); #endif /* _SMB2PROTO_H */ From 07fa6010ff939ef6c5a9ae0d230fdfd3bad82722 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 27 Nov 2018 10:01:51 +1100 Subject: [PATCH 08/39] cifs: suppress some implicit-fallthrough warnings To avoid the warning: warning: this statement may fall through [-Wimplicit-fallthrough=] Signed-off-by: Gustavo A. R. Silva Reviewed-by: Stephen Rothwell Signed-off-by: Steve French --- fs/cifs/connect.c | 2 +- fs/cifs/sess.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index f0be8ed6871f..e4a924ba6325 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -1828,7 +1828,7 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, vol->password = NULL; break; } - /* Yes it is. Drop down to Opt_pass below.*/ + /* Fallthrough - to Opt_pass below.*/ case Opt_pass: /* Obtain the value string */ value = strchr(data, '='); diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c index aa23c00367ec..7fa447aa2b50 100644 --- a/fs/cifs/sess.c +++ b/fs/cifs/sess.c @@ -534,9 +534,9 @@ cifs_select_sectype(struct TCP_Server_Info *server, enum securityEnum requested) if (global_secflags & CIFSSEC_MAY_NTLM) return NTLM; default: - /* Fallthrough to attempt LANMAN authentication next */ break; } + /* Fallthrough - to attempt LANMAN authentication next */ case CIFS_NEGFLAVOR_LANMAN: switch (requested) { case LANMAN: From 0f57451eebf1945790785df106497bf62812f62a Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Fri, 7 Dec 2018 07:05:38 +0000 Subject: [PATCH 09/39] cifs: remove set but not used variable 'smb_buf' Fixes gcc '-Wunused-but-set-variable' warning: fs/cifs/sess.c: In function '_sess_auth_rawntlmssp_assemble_req': fs/cifs/sess.c:1157:18: warning: variable 'smb_buf' set but not used [-Wunused-but-set-variable] It never used since commit cc87c47d9d7a ("cifs: Separate rawntlmssp auth from CIFS_SessSetup()") Signed-off-by: YueHaibing Signed-off-by: Steve French --- fs/cifs/sess.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c index 7fa447aa2b50..dcd49ad60c83 100644 --- a/fs/cifs/sess.c +++ b/fs/cifs/sess.c @@ -1154,14 +1154,12 @@ out: static int _sess_auth_rawntlmssp_assemble_req(struct sess_data *sess_data) { - struct smb_hdr *smb_buf; SESSION_SETUP_ANDX *pSMB; struct cifs_ses *ses = sess_data->ses; __u32 capabilities; char *bcc_ptr; pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base; - smb_buf = (struct smb_hdr *)pSMB; capabilities = cifs_ssetup_hdr(ses, pSMB); if ((pSMB->req.hdr.Flags2 & SMBFLG2_UNICODE) == 0) { From 52baa51d30573273341b78a3e3ed0d9a845d3e55 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 12 Dec 2018 11:50:00 +1000 Subject: [PATCH 10/39] cifs: remove coverity warning in calc_lanman_hash password_with_pad is a fixed size buffer of 16 bytes, it contains a password string, to be padded with \0 if shorter than 16 bytes but is just truncated if longer. It is not, and we do not depend on it to be, nul terminated. As such, do not use strncpy() to populate this buffer since the str* prefix suggests that this is a string, which it is not, and it also confuses coverity causing a false warning. Detected by CoverityScan CID#113743 ("Buffer not null terminated") Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/cifsencrypt.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/cifs/cifsencrypt.c b/fs/cifs/cifsencrypt.c index ecdff344e8fe..d2a05e46d6f5 100644 --- a/fs/cifs/cifsencrypt.c +++ b/fs/cifs/cifsencrypt.c @@ -304,12 +304,17 @@ int setup_ntlm_response(struct cifs_ses *ses, const struct nls_table *nls_cp) int calc_lanman_hash(const char *password, const char *cryptkey, bool encrypt, char *lnm_session_key) { - int i; + int i, len; int rc; char password_with_pad[CIFS_ENCPWD_SIZE] = {0}; - if (password) - strncpy(password_with_pad, password, CIFS_ENCPWD_SIZE); + if (password) { + for (len = 0; len < CIFS_ENCPWD_SIZE; len++) + if (!password[len]) + break; + + memcpy(password_with_pad, password, len); + } if (!encrypt && global_secflags & CIFSSEC_MAY_PLNTXT) { memcpy(lnm_session_key, password_with_pad, From 59a63e479ce36a3f24444c3a36efe82b78e4a8e0 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 13 Dec 2018 08:06:16 +1000 Subject: [PATCH 11/39] cifs: check ntwrk_buf_start for NULL before dereferencing it RHBZ: 1021460 There is an issue where when multiple threads open/close the same directory ntwrk_buf_start might end up being NULL, causing the call to smbCalcSize later to oops with a NULL deref. The real bug is why this happens and why this can become NULL for an open cfile, which should not be allowed. This patch tries to avoid a oops until the time when we fix the underlying issue. Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/readdir.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c index e169e1a5fd35..3925a7bfc74d 100644 --- a/fs/cifs/readdir.c +++ b/fs/cifs/readdir.c @@ -655,7 +655,14 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, /* scan and find it */ int i; char *cur_ent; - char *end_of_smb = cfile->srch_inf.ntwrk_buf_start + + char *end_of_smb; + + if (cfile->srch_inf.ntwrk_buf_start == NULL) { + cifs_dbg(VFS, "ntwrk_buf_start is NULL during readdir\n"); + return -EIO; + } + + end_of_smb = cfile->srch_inf.ntwrk_buf_start + server->ops->calc_smb_size( cfile->srch_inf.ntwrk_buf_start, server); From b6bc8a7b993e62f82415a5e3e4a6469e80fea19c Mon Sep 17 00:00:00 2001 From: Long Li Date: Sun, 16 Dec 2018 23:17:04 +0000 Subject: [PATCH 12/39] CIFS: use the correct length when pinning memory for direct I/O for write The current code attempts to pin memory using the largest possible wsize based on the currect SMB credits. This doesn't cause kernel oops but this is not optimal as we may pin more pages then actually needed. Fix this by only pinning what are needed for doing this write I/O. Signed-off-by: Long Li Cc: stable@vger.kernel.org Signed-off-by: Steve French Reviewed-by: Joey Pabalinas --- fs/cifs/file.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/cifs/file.c b/fs/cifs/file.c index c9bc56b1baac..179991435777 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -2617,11 +2617,13 @@ cifs_write_from_iter(loff_t offset, size_t len, struct iov_iter *from, if (rc) break; + cur_len = min_t(const size_t, len, wsize); + if (ctx->direct_io) { ssize_t result; result = iov_iter_get_pages_alloc( - from, &pagevec, wsize, &start); + from, &pagevec, cur_len, &start); if (result < 0) { cifs_dbg(VFS, "direct_writev couldn't get user pages " From 54e94ff94eac887ddb59cfd46b18896da5695e35 Mon Sep 17 00:00:00 2001 From: Long Li Date: Sun, 16 Dec 2018 22:41:07 +0000 Subject: [PATCH 13/39] CIFS: return correct errors when pinning memory failed for direct I/O When pinning memory failed, we should return the correct error code and rewind the SMB credits. Reported-by: Murphy Zhou Signed-off-by: Long Li Cc: stable@vger.kernel.org Cc: Murphy Zhou Signed-off-by: Steve French --- fs/cifs/file.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 179991435777..c23bf9da93d2 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -2632,6 +2632,9 @@ cifs_write_from_iter(loff_t offset, size_t len, struct iov_iter *from, result, from->type, from->iov_offset, from->count); dump_stack(); + + rc = result; + add_credits_and_wake_if(server, credits, 0); break; } cur_len = (size_t)result; @@ -3315,13 +3318,16 @@ cifs_send_async_read(loff_t offset, size_t len, struct cifsFileInfo *open_file, cur_len, &start); if (result < 0) { cifs_dbg(VFS, - "couldn't get user pages (cur_len=%zd)" + "couldn't get user pages (rc=%zd)" " iter type %d" " iov_offset %zd count %zd\n", result, direct_iov.type, direct_iov.iov_offset, direct_iov.count); dump_stack(); + + rc = result; + add_credits_and_wake_if(server, credits, 0); break; } cur_len = (size_t)result; From 9a596f5b39593414c0ec80f71b94a226286f084e Mon Sep 17 00:00:00 2001 From: Georgy A Bystrenin Date: Fri, 21 Dec 2018 00:11:42 -0600 Subject: [PATCH 14/39] CIFS: Fix error mapping for SMB2_LOCK command which caused OFD lock problem While resolving a bug with locks on samba shares found a strange behavior. When a file locked by one node and we trying to lock it from another node it fail with errno 5 (EIO) but in that case errno must be set to (EACCES | EAGAIN). This isn't happening when we try to lock file second time on same node. In this case it returns EACCES as expected. Also this issue not reproduces when we use SMB1 protocol (vers=1.0 in mount options). Further investigation showed that the mapping from status_to_posix_error is different for SMB1 and SMB2+ implementations. For SMB1 mapping is [NT_STATUS_LOCK_NOT_GRANTED to ERRlock] (See fs/cifs/netmisc.c line 66) but for SMB2+ mapping is [STATUS_LOCK_NOT_GRANTED to -EIO] (see fs/cifs/smb2maperror.c line 383) Quick changes in SMB2+ mapping from EIO to EACCES has fixed issue. BUG: https://bugzilla.kernel.org/show_bug.cgi?id=201971 Signed-off-by: Georgy A Bystrenin Reviewed-by: Pavel Shilovsky CC: Stable Signed-off-by: Steve French --- fs/cifs/smb2maperror.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/cifs/smb2maperror.c b/fs/cifs/smb2maperror.c index d47b7f5dfa6c..924269cec135 100644 --- a/fs/cifs/smb2maperror.c +++ b/fs/cifs/smb2maperror.c @@ -379,8 +379,8 @@ static const struct status_to_posix_error smb2_error_map_table[] = { {STATUS_NONEXISTENT_EA_ENTRY, -EIO, "STATUS_NONEXISTENT_EA_ENTRY"}, {STATUS_NO_EAS_ON_FILE, -ENODATA, "STATUS_NO_EAS_ON_FILE"}, {STATUS_EA_CORRUPT_ERROR, -EIO, "STATUS_EA_CORRUPT_ERROR"}, - {STATUS_FILE_LOCK_CONFLICT, -EIO, "STATUS_FILE_LOCK_CONFLICT"}, - {STATUS_LOCK_NOT_GRANTED, -EIO, "STATUS_LOCK_NOT_GRANTED"}, + {STATUS_FILE_LOCK_CONFLICT, -EACCES, "STATUS_FILE_LOCK_CONFLICT"}, + {STATUS_LOCK_NOT_GRANTED, -EACCES, "STATUS_LOCK_NOT_GRANTED"}, {STATUS_DELETE_PENDING, -ENOENT, "STATUS_DELETE_PENDING"}, {STATUS_CTL_FILE_NOT_SUPPORTED, -ENOSYS, "STATUS_CTL_FILE_NOT_SUPPORTED"}, From 56c762eb9bee330bb4e6d11c589434f2904d3ab6 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 13:03:14 -0200 Subject: [PATCH 15/39] cifs: Refactor out cifs_mount() * Split and refactor the very large function cifs_mount() in multiple functions: - tcp, ses and tcon setup to mount_get_conns() - tcp, ses and tcon cleanup in mount_put_conns() - tcon tlink setup to mount_setup_tlink() - remote path checking to is_path_remote() * Implement 2 version of cifs_mount() for DFS-enabled builds and non-DFS-enabled builds (CONFIG_CIFS_DFS_UPCALL). In preparation for DFS failover support. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifsproto.h | 4 +- fs/cifs/connect.c | 461 ++++++++++++++++++++++++++------------------ 2 files changed, 272 insertions(+), 193 deletions(-) diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index fa361bc00602..f4dd2a3795dd 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -213,7 +213,7 @@ extern int cifs_match_super(struct super_block *, void *); extern void cifs_cleanup_volume_info(struct smb_vol *pvolume_info); extern struct smb_vol *cifs_get_volume_info(char *mount_data, const char *devname, bool is_smb3); -extern int cifs_mount(struct cifs_sb_info *, struct smb_vol *); +extern int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol); extern void cifs_umount(struct cifs_sb_info *); extern void cifs_mark_open_files_invalid(struct cifs_tcon *tcon); extern void cifs_reopen_persistent_handles(struct cifs_tcon *tcon); @@ -524,6 +524,8 @@ extern int E_md4hash(const unsigned char *passwd, unsigned char *p16, const struct nls_table *codepage); extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8, unsigned char *p24); +extern void +cifs_cleanup_volume_info_contents(struct smb_vol *volume_info); void cifs_readdata_release(struct kref *refcount); int cifs_async_readv(struct cifs_readdata *rdata); diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index e4a924ba6325..944188d6200c 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3747,8 +3747,8 @@ int cifs_setup_cifs_sb(struct smb_vol *pvolume_info, return 0; } -static void -cleanup_volume_info_contents(struct smb_vol *volume_info) +void +cifs_cleanup_volume_info_contents(struct smb_vol *volume_info) { kfree(volume_info->username); kzfree(volume_info->password); @@ -3763,10 +3763,136 @@ cifs_cleanup_volume_info(struct smb_vol *volume_info) { if (!volume_info) return; - cleanup_volume_info_contents(volume_info); + cifs_cleanup_volume_info_contents(volume_info); kfree(volume_info); } +/* Release all succeed connections */ +static inline void mount_put_conns(struct cifs_sb_info *cifs_sb, + unsigned int xid, + struct TCP_Server_Info *server, + struct cifs_ses *ses, struct cifs_tcon *tcon) +{ + int rc = 0; + + if (tcon) + cifs_put_tcon(tcon); + else if (ses) + cifs_put_smb_ses(ses); + else if (server) + cifs_put_tcp_session(server, 0); + cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS; + free_xid(xid); +} + +/* Get connections for tcp, ses and tcon */ +static int mount_get_conns(struct smb_vol *vol, struct cifs_sb_info *cifs_sb, + unsigned int *xid, + struct TCP_Server_Info **nserver, + struct cifs_ses **nses, struct cifs_tcon **ntcon) +{ + int rc = 0; + struct TCP_Server_Info *server; + struct cifs_ses *ses; + struct cifs_tcon *tcon; + + *nserver = NULL; + *nses = NULL; + *ntcon = NULL; + + *xid = get_xid(); + + /* get a reference to a tcp session */ + server = cifs_get_tcp_session(vol); + if (IS_ERR(server)) { + rc = PTR_ERR(server); + return rc; + } + + *nserver = server; + + if ((vol->max_credits < 20) || (vol->max_credits > 60000)) + server->max_credits = SMB2_MAX_CREDITS_AVAILABLE; + else + server->max_credits = vol->max_credits; + + /* get a reference to a SMB session */ + ses = cifs_get_smb_ses(server, vol); + if (IS_ERR(ses)) { + rc = PTR_ERR(ses); + return rc; + } + + *nses = ses; + + if ((vol->persistent == true) && (!(ses->server->capabilities & + SMB2_GLOBAL_CAP_PERSISTENT_HANDLES))) { + cifs_dbg(VFS, "persistent handles not supported by server\n"); + return -EOPNOTSUPP; + } + + /* search for existing tcon to this server share */ + tcon = cifs_get_tcon(ses, vol); + if (IS_ERR(tcon)) { + rc = PTR_ERR(tcon); + return rc; + } + + *ntcon = tcon; + + /* if new SMB3.11 POSIX extensions are supported do not remap / and \ */ + if (tcon->posix_extensions) + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS; + + /* tell server which Unix caps we support */ + if (cap_unix(tcon->ses)) { + /* + * reset of caps checks mount to see if unix extensions disabled + * for just this mount. + */ + reset_cifs_unix_caps(*xid, tcon, cifs_sb, vol); + if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) && + (le64_to_cpu(tcon->fsUnixInfo.Capability) & + CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) + return -EACCES; + } else + tcon->unix_ext = 0; /* server does not support them */ + + /* do not care if a following call succeed - informational */ + if (!tcon->pipe && server->ops->qfs_tcon) + server->ops->qfs_tcon(*xid, tcon); + + cifs_sb->wsize = server->ops->negotiate_wsize(tcon, vol); + cifs_sb->rsize = server->ops->negotiate_rsize(tcon, vol); + + return 0; +} + +static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, + struct cifs_tcon *tcon) +{ + struct tcon_link *tlink; + + /* hang the tcon off of the superblock */ + tlink = kzalloc(sizeof(*tlink), GFP_KERNEL); + if (tlink == NULL) + return -ENOMEM; + + tlink->tl_uid = ses->linux_uid; + tlink->tl_tcon = tcon; + tlink->tl_time = jiffies; + set_bit(TCON_LINK_MASTER, &tlink->tl_flags); + set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags); + + cifs_sb->master_tlink = tlink; + spin_lock(&cifs_sb->tlink_tree_lock); + tlink_rb_insert(&cifs_sb->tlink_tree, tlink); + spin_unlock(&cifs_sb->tlink_tree_lock); + + queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks, + TLINK_IDLE_EXPIRE); + return 0; +} #ifdef CONFIG_CIFS_DFS_UPCALL /* @@ -3846,7 +3972,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, rc = PTR_ERR(mdata); mdata = NULL; } else { - cleanup_volume_info_contents(volume_info); + cifs_cleanup_volume_info_contents(volume_info); rc = cifs_setup_volume_info(volume_info, mdata, fake_devname, false); } @@ -3955,107 +4081,77 @@ cifs_are_all_path_components_accessible(struct TCP_Server_Info *server, return rc; } -int -cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info) +/* + * Check if path is remote (e.g. a DFS share). Return -EREMOTE if it is, + * otherwise 0. + */ +static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb_vol *vol, + const unsigned int xid, + struct TCP_Server_Info *server, + struct cifs_tcon *tcon) { int rc; + char *full_path; + + if (!server->ops->is_path_accessible) + return -EOPNOTSUPP; + + /* + * cifs_build_path_to_root works only when we have a valid tcon + */ + full_path = cifs_build_path_to_root(vol, cifs_sb, tcon, + tcon->Flags & SMB_SHARE_IS_IN_DFS); + if (full_path == NULL) + return -ENOMEM; + + cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); + + rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, + full_path); + if (rc != 0 && rc != -EREMOTE) { + kfree(full_path); + return rc; + } + + if (rc != -EREMOTE) { + rc = cifs_are_all_path_components_accessible(server, xid, tcon, + cifs_sb, + full_path); + if (rc != 0) { + cifs_dbg(VFS, "cannot query dirs between root and final path, " + "enabling CIFS_MOUNT_USE_PREFIX_PATH\n"); + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH; + rc = 0; + } + } + + kfree(full_path); + return rc; +} + +#ifdef CONFIG_CIFS_DFS_UPCALL +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 *tcon; + struct cifs_tcon *tcon = NULL; struct TCP_Server_Info *server; - char *full_path; - struct tcon_link *tlink; -#ifdef CONFIG_CIFS_DFS_UPCALL - int referral_walks_count = 0; -#endif + char *old_mountdata; + int count; -#ifdef CONFIG_CIFS_DFS_UPCALL -try_mount_again: - /* cleanup activities if we're chasing a referral */ - if (referral_walks_count) { - if (tcon) - cifs_put_tcon(tcon); - else if (ses) - cifs_put_smb_ses(ses); - - cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS; - - free_xid(xid); + rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); + if (!rc && tcon) { + rc = is_path_remote(cifs_sb, vol, xid, server, tcon); + if (!rc) + goto out; + if (rc != -EREMOTE) + goto error; } -#endif - rc = 0; - tcon = NULL; - ses = NULL; - server = NULL; - full_path = NULL; - tlink = NULL; + if ((rc == -EACCES) || (rc == -EOPNOTSUPP) || (ses == NULL) || (server == NULL)) + goto error; - xid = get_xid(); - /* get a reference to a tcp session */ - server = cifs_get_tcp_session(volume_info); - if (IS_ERR(server)) { - rc = PTR_ERR(server); - goto out; - } - if ((volume_info->max_credits < 20) || - (volume_info->max_credits > 60000)) - server->max_credits = SMB2_MAX_CREDITS_AVAILABLE; - else - server->max_credits = volume_info->max_credits; - /* get a reference to a SMB session */ - ses = cifs_get_smb_ses(server, volume_info); - if (IS_ERR(ses)) { - rc = PTR_ERR(ses); - ses = NULL; - goto mount_fail_check; - } - - if ((volume_info->persistent == true) && ((ses->server->capabilities & - SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) == 0)) { - cifs_dbg(VFS, "persistent handles not supported by server\n"); - rc = -EOPNOTSUPP; - goto mount_fail_check; - } - - /* search for existing tcon to this server share */ - tcon = cifs_get_tcon(ses, volume_info); - if (IS_ERR(tcon)) { - rc = PTR_ERR(tcon); - tcon = NULL; - if (rc == -EACCES) - goto mount_fail_check; - - goto remote_path_check; - } - - /* if new SMB3.11 POSIX extensions are supported do not remap / and \ */ - if (tcon->posix_extensions) - cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS; - - /* tell server which Unix caps we support */ - if (cap_unix(tcon->ses)) { - /* reset of caps checks mount to see if unix extensions - disabled for just this mount */ - reset_cifs_unix_caps(xid, tcon, cifs_sb, volume_info); - if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) && - (le64_to_cpu(tcon->fsUnixInfo.Capability) & - CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) { - rc = -EACCES; - goto mount_fail_check; - } - } else - tcon->unix_ext = 0; /* server does not support them */ - - /* do not care if a following call succeed - informational */ - if (!tcon->pipe && server->ops->qfs_tcon) - server->ops->qfs_tcon(xid, tcon); - - cifs_sb->wsize = server->ops->negotiate_wsize(tcon, volume_info); - cifs_sb->rsize = server->ops->negotiate_rsize(tcon, volume_info); - -remote_path_check: -#ifdef CONFIG_CIFS_DFS_UPCALL /* * Perform an unconditional check for whether there are DFS * referrals for this path without prefix, to provide support @@ -4063,119 +4159,100 @@ remote_path_check: * with PATH_NOT_COVERED to requests that include the prefix. * Chase the referral if found, otherwise continue normally. */ - if (referral_walks_count == 0) { - int refrc = expand_dfs_referral(xid, ses, volume_info, cifs_sb, - false); - if (!refrc) { - referral_walks_count++; - goto try_mount_again; - } - } -#endif + old_mountdata = cifs_sb->mountdata; + (void)expand_dfs_referral(xid, ses, vol, cifs_sb, false); - /* check if a whole path is not remote */ - if (!rc && tcon) { - if (!server->ops->is_path_accessible) { - rc = -ENOSYS; - goto mount_fail_check; + if (cifs_sb->mountdata == NULL) { + rc = -ENOENT; + 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; + } + + for (count = 1; ;) { + if (!rc && tcon) { + rc = is_path_remote(cifs_sb, vol, xid, server, tcon); + if (!rc || rc != -EREMOTE) + break; } /* - * cifs_build_path_to_root works only when we have a valid tcon + * 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). */ - full_path = cifs_build_path_to_root(volume_info, cifs_sb, tcon, - tcon->Flags & SMB_SHARE_IS_IN_DFS); - if (full_path == NULL) { - rc = -ENOMEM; - goto mount_fail_check; - } - rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, - full_path); - if (rc != 0 && rc != -EREMOTE) { - kfree(full_path); - goto mount_fail_check; - } - - if (rc != -EREMOTE) { - rc = cifs_are_all_path_components_accessible(server, - xid, tcon, cifs_sb, - full_path); - if (rc != 0) { - cifs_dbg(VFS, "cannot query dirs between root and final path, " - "enabling CIFS_MOUNT_USE_PREFIX_PATH\n"); - cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH; - rc = 0; - } - } - kfree(full_path); - } - - /* get referral if needed */ - if (rc == -EREMOTE) { -#ifdef CONFIG_CIFS_DFS_UPCALL - if (referral_walks_count > MAX_NESTED_LINKS) { - /* - * 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; - goto mount_fail_check; + break; } - rc = expand_dfs_referral(xid, ses, volume_info, cifs_sb, true); + old_mountdata = cifs_sb->mountdata; + rc = expand_dfs_referral(xid, tcon->ses, vol, cifs_sb, + true); + if (rc) + break; - if (!rc) { - referral_walks_count++; - goto try_mount_again; + if (cifs_sb->mountdata != old_mountdata) { + 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 || !server || + !ses) + goto error; } - goto mount_fail_check; -#else /* No DFS support, return error on mount */ - rc = -EOPNOTSUPP; -#endif } if (rc) - goto mount_fail_check; - - /* now, hang the tcon off of the superblock */ - tlink = kzalloc(sizeof *tlink, GFP_KERNEL); - if (tlink == NULL) { - rc = -ENOMEM; - goto mount_fail_check; - } - - tlink->tl_uid = ses->linux_uid; - tlink->tl_tcon = tcon; - tlink->tl_time = jiffies; - set_bit(TCON_LINK_MASTER, &tlink->tl_flags); - set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags); - - cifs_sb->master_tlink = tlink; - spin_lock(&cifs_sb->tlink_tree_lock); - tlink_rb_insert(&cifs_sb->tlink_tree, tlink); - spin_unlock(&cifs_sb->tlink_tree_lock); - - queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks, - TLINK_IDLE_EXPIRE); - -mount_fail_check: - /* on error free sesinfo and tcon struct if needed */ - if (rc) { - /* If find_unc succeeded then rc == 0 so we can not end */ - /* up accidentally freeing someone elses tcon struct */ - if (tcon) - cifs_put_tcon(tcon); - else if (ses) - cifs_put_smb_ses(ses); - else - cifs_put_tcp_session(server, 0); - } + goto error; out: free_xid(xid); + return mount_setup_tlink(cifs_sb, ses, tcon); + +error: + mount_put_conns(cifs_sb, xid, server, ses, tcon); return rc; } +#else +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 *tcon; + struct TCP_Server_Info *server; + + rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); + if (rc) + goto error; + + if (tcon) { + rc = is_path_remote(cifs_sb, vol, xid, server, tcon); + if (rc == -EREMOTE) + rc = -EOPNOTSUPP; + if (rc) + goto error; + } + + free_xid(xid); + + return mount_setup_tlink(cifs_sb, ses, tcon); + +error: + mount_put_conns(cifs_sb, xid, server, ses, tcon); + return rc; +} +#endif /* * Issue a TREE_CONNECT request. From c34fea5a636d98607ee6e41c78acc9d5ca8fb756 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 14:03:40 -0200 Subject: [PATCH 16/39] cifs: Skip any trailing backslashes from UNC When extracting hostname from UNC, check for leading backslashes before trying to remove them. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 944188d6200c..bab4422f5815 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -1044,7 +1044,12 @@ extract_hostname(const char *unc) /* skip double chars at beginning of string */ /* BB: check validity of these bytes? */ - src = unc + 2; + if (strlen(unc) < 3) + return ERR_PTR(-EINVAL); + for (src = unc; *src && *src == '\\'; src++) + ; + if (!*src) + return ERR_PTR(-EINVAL); /* delimiter between hostname and sharename is always '\\' now */ delim = strchr(src, '\\'); From d9345e0ae7cff25e9aed39d21f092dbb482dffec Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 14:18:22 -0200 Subject: [PATCH 17/39] cifs: Make devname param optional in cifs_compose_mount_options() If we only want to get the mount options strings, do not return the devname. For DFS failover, we'll be passing the DFS full path down to cifs_mount() rather than the devname. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifs_dfs_ref.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c index b97c74efd04a..7adbdf9eb137 100644 --- a/fs/cifs/cifs_dfs_ref.c +++ b/fs/cifs/cifs_dfs_ref.c @@ -126,7 +126,7 @@ cifs_build_devname(char *nodename, const char *prepath) * @sb_mountdata: parent/root DFS mount options (template) * @fullpath: full path in UNC format * @ref: server's referral - * @devname: pointer for saving device name + * @devname: optional pointer for saving device name * * creates mount options for submount based on template options sb_mountdata * and replacing unc,ip,prefixpath options with ones we've got form ref_unc. @@ -140,6 +140,7 @@ char *cifs_compose_mount_options(const char *sb_mountdata, char **devname) { int rc; + char *name; char *mountdata = NULL; const char *prepath = NULL; int md_len; @@ -158,17 +159,17 @@ char *cifs_compose_mount_options(const char *sb_mountdata, prepath++; } - *devname = cifs_build_devname(ref->node_name, prepath); - if (IS_ERR(*devname)) { - rc = PTR_ERR(*devname); - *devname = NULL; + name = cifs_build_devname(ref->node_name, prepath); + if (IS_ERR(name)) { + rc = PTR_ERR(name); + name = NULL; goto compose_mount_options_err; } - rc = dns_resolve_server_name_to_ip(*devname, &srvIP); + rc = dns_resolve_server_name_to_ip(name, &srvIP); if (rc < 0) { cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n", - __func__, *devname, rc); + __func__, name, rc); goto compose_mount_options_err; } @@ -224,6 +225,9 @@ char *cifs_compose_mount_options(const char *sb_mountdata, strcat(mountdata, "ip="); strcat(mountdata, srvIP); + if (devname) + *devname = name; + /*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/ /*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/ @@ -234,8 +238,7 @@ compose_mount_options_out: compose_mount_options_err: kfree(mountdata); mountdata = ERR_PTR(rc); - kfree(*devname); - *devname = NULL; + kfree(name); goto compose_mount_options_out; } From 5fc7fcd054adcf0f264446cbf778d33e30a45455 Mon Sep 17 00:00:00 2001 From: Aurelien Aptel Date: Fri, 16 Nov 2018 16:13:25 +0100 Subject: [PATCH 18/39] cifs: auto disable 'serverino' in dfs mounts Different servers have different set of file ids. After failover, unique IDs will be different so we can't validate them. Signed-off-by: Aurelien Aptel Reviewed-by: Paulo Alcantara Signed-off-by: Steve French --- fs/cifs/connect.c | 6 ++++++ fs/cifs/inode.c | 42 +++++++++++++++++++----------------------- fs/cifs/misc.c | 12 ++++++++++-- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index bab4422f5815..d0de4fb8ee43 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -4220,6 +4220,12 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) if (rc) goto error; + /* + * After reconnecting to a different server, unique ids won't + * match anymore, so we disable serverino. This prevents + * dentry revalidation to think the dentry are stale (ESTALE). + */ + cifs_autodisable_serverino(cifs_sb); out: free_xid(xid); return mount_setup_tlink(cifs_sb, ses, tcon); diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index 1fe9f3b01703..13fb59aadebc 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -730,7 +730,6 @@ cifs_get_inode_info(struct inode **inode, const char *full_path, FILE_ALL_INFO *data, struct super_block *sb, int xid, const struct cifs_fid *fid) { - bool validinum = false; __u16 srchflgs; int rc = 0, tmprc = ENOSYS; struct cifs_tcon *tcon; @@ -821,7 +820,6 @@ cifs_get_inode_info(struct inode **inode, const char *full_path, (FILE_DIRECTORY_INFO *)data, cifs_sb); fattr.cf_uniqueid = le64_to_cpu( ((SEARCH_ID_FULL_DIR_INFO *)data)->UniqueId); - validinum = true; cifs_buf_release(srchinf->ntwrk_buf_start); } @@ -840,31 +838,29 @@ cifs_get_inode_info(struct inode **inode, const char *full_path, */ if (*inode == NULL) { if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) { - if (validinum == false) { - if (server->ops->get_srv_inum) - tmprc = server->ops->get_srv_inum(xid, - tcon, cifs_sb, full_path, - &fattr.cf_uniqueid, data); - if (tmprc) { - cifs_dbg(FYI, "GetSrvInodeNum rc %d\n", - tmprc); - fattr.cf_uniqueid = iunique(sb, ROOT_I); - cifs_autodisable_serverino(cifs_sb); - } else if ((fattr.cf_uniqueid == 0) && - strlen(full_path) == 0) { - /* some servers ret bad root ino ie 0 */ - cifs_dbg(FYI, "Invalid (0) inodenum\n"); - fattr.cf_flags |= - CIFS_FATTR_FAKE_ROOT_INO; - fattr.cf_uniqueid = - simple_hashstr(tcon->treeName); - } + if (server->ops->get_srv_inum) + tmprc = server->ops->get_srv_inum(xid, + tcon, cifs_sb, full_path, + &fattr.cf_uniqueid, data); + if (tmprc) { + cifs_dbg(FYI, "GetSrvInodeNum rc %d\n", + tmprc); + fattr.cf_uniqueid = iunique(sb, ROOT_I); + cifs_autodisable_serverino(cifs_sb); + } else if ((fattr.cf_uniqueid == 0) && + strlen(full_path) == 0) { + /* some servers ret bad root ino ie 0 */ + cifs_dbg(FYI, "Invalid (0) inodenum\n"); + fattr.cf_flags |= + CIFS_FATTR_FAKE_ROOT_INO; + fattr.cf_uniqueid = + simple_hashstr(tcon->treeName); } } else fattr.cf_uniqueid = iunique(sb, ROOT_I); } else { - if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) && - validinum == false && server->ops->get_srv_inum) { + if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) + && server->ops->get_srv_inum) { /* * Pass a NULL tcon to ensure we don't make a round * trip to the server. This only works for SMB2+. diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index 8a41f4eba726..f7c0c6fde552 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -525,9 +525,17 @@ void cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb) { if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) { + struct cifs_tcon *tcon = NULL; + + if (cifs_sb->master_tlink) + tcon = cifs_sb_master_tcon(cifs_sb); + cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_SERVER_INUM; - cifs_dbg(VFS, "Autodisabling the use of server inode numbers on %s. This server doesn't seem to support them properly. Hardlinks will not be recognized on this mount. Consider mounting with the \"noserverino\" option to silence this message.\n", - cifs_sb_master_tcon(cifs_sb)->treeName); + cifs_dbg(VFS, "Autodisabling the use of server inode numbers on %s.\n", + tcon ? tcon->treeName : "new server"); + cifs_dbg(VFS, "The server doesn't seem to support them properly or the files might be on different servers (DFS).\n"); + cifs_dbg(VFS, "Hardlinks will not be recognized on this mount. Consider mounting with the \"noserverino\" option to silence this message.\n"); + } } From e7b602f43719fc6173ae86d2de8f6f07c6858ddd Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 15:38:51 -0200 Subject: [PATCH 19/39] cifs: Save TTL value when parsing DFS referrals This will be needed by DFS cache. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifsglob.h | 1 + fs/cifs/misc.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 7b3b9313dcde..3b1aa12708a0 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1508,6 +1508,7 @@ struct dfs_info3_param { int ref_flag; char *path_name; char *node_name; + int ttl; }; /* diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index f7c0c6fde552..5e315e4009e2 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -740,6 +740,8 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, goto parse_DFS_referrals_exit; } + node->ttl = le32_to_cpu(ref->TimeToLive); + ref++; } From 54be1f6c1c37498bba557049df646cc239fa37e3 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 16:01:21 -0200 Subject: [PATCH 20/39] cifs: Add DFS cache routines * Add new dfs_cache.[ch] files * Add new /proc/fs/cifs/dfscache file - dump current cache when read - clear current cache when writing "0" to it * Add delayed_work to periodically refresh cache entries The new interface will be used for caching DFS referrals, as well as supporting client target failover. The DFS cache is a hashtable that maps UNC paths to cache entries. A cache entry contains: - the UNC path it is mapped on - how much the the UNC path the entry consumes - flags - a Time-To-Live after which the entry expires - a list of possible targets (linked lists of UNC paths) - a "hint target" pointing the last known working target or the first target if none were tried. This hint lets cifs.ko remember and try working targets first. * Looking for an entry in the cache is done with dfs_cache_find() - if no valid entries are found, a DFS query is made, stored in the cache and returned - the full target list can be copied and returned to avoid race conditions and looped on with the help with the dfs_cache_tgt_iterator * Updating the target hint to the next target is done with dfs_cache_update_tgthint() These functions have a dfs_cache_noreq_XXX() version that doesn't fetches referrals if no entries are found. These versions don't require the tcp/ses/tcon/cifs_sb parameters as a result. Expired entries cannot be used and since they have a pretty short TTL [1] in order for them to be useful for failover the DFS cache adds a delayed work called periodically to keep them fresh. Since we might not have available connections to issue the referral request when refreshing we need to store volume_info structs with credentials and other needed info to be able to connect to the right server. 1: Windows defaults: 5mn for domain-based referrals, 30mn for regular links Signed-off-by: Paulo Alcantara Signed-off-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/Makefile | 2 +- fs/cifs/cifs_debug.c | 12 + fs/cifs/cifsglob.h | 5 + fs/cifs/cifsproto.h | 3 + fs/cifs/connect.c | 2 +- fs/cifs/dfs_cache.c | 1365 ++++++++++++++++++++++++++++++++++++++++++ fs/cifs/dfs_cache.h | 97 +++ 7 files changed, 1484 insertions(+), 2 deletions(-) create mode 100644 fs/cifs/dfs_cache.c create mode 100644 fs/cifs/dfs_cache.h diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile index 85817991ee68..51af69a1a328 100644 --- a/fs/cifs/Makefile +++ b/fs/cifs/Makefile @@ -17,7 +17,7 @@ cifs-$(CONFIG_CIFS_ACL) += cifsacl.o cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o -cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o +cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o dfs_cache.o cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o diff --git a/fs/cifs/cifs_debug.c b/fs/cifs/cifs_debug.c index ba178b09de0b..593fb422d0f3 100644 --- a/fs/cifs/cifs_debug.c +++ b/fs/cifs/cifs_debug.c @@ -30,6 +30,9 @@ #include "cifsproto.h" #include "cifs_debug.h" #include "cifsfs.h" +#ifdef CONFIG_CIFS_DFS_UPCALL +#include "dfs_cache.h" +#endif #ifdef CONFIG_CIFS_SMB_DIRECT #include "smbdirect.h" #endif @@ -629,6 +632,11 @@ cifs_proc_init(void) &cifs_security_flags_proc_fops); proc_create("LookupCacheEnabled", 0644, proc_fs_cifs, &cifs_lookup_cache_proc_fops); + +#ifdef CONFIG_CIFS_DFS_UPCALL + proc_create("dfscache", 0644, proc_fs_cifs, &dfscache_proc_fops); +#endif + #ifdef CONFIG_CIFS_SMB_DIRECT proc_create("rdma_readwrite_threshold", 0644, proc_fs_cifs, &cifs_rdma_readwrite_threshold_proc_fops); @@ -663,6 +671,10 @@ cifs_proc_clean(void) remove_proc_entry("SecurityFlags", proc_fs_cifs); remove_proc_entry("LinuxExtensionsEnabled", proc_fs_cifs); remove_proc_entry("LookupCacheEnabled", proc_fs_cifs); + +#ifdef CONFIG_CIFS_DFS_UPCALL + remove_proc_entry("dfscache", proc_fs_cifs); +#endif #ifdef CONFIG_CIFS_SMB_DIRECT remove_proc_entry("rdma_readwrite_threshold", proc_fs_cifs); remove_proc_entry("smbd_max_frmr_depth", proc_fs_cifs); diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 3b1aa12708a0..66c487634c9e 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1014,6 +1014,11 @@ struct cifs_tcon { struct list_head pending_opens; /* list of incomplete opens */ struct cached_fid crfid; /* Cached root fid */ /* BB add field for back pointer to sb struct(s)? */ +#ifdef CONFIG_CIFS_DFS_UPCALL + char *dfs_path; + int remap:2; + struct list_head ulist; /* cache update list */ +#endif }; /* diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index f4dd2a3795dd..efa5e36b3762 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -527,6 +527,9 @@ extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8, extern void cifs_cleanup_volume_info_contents(struct smb_vol *volume_info); +extern struct TCP_Server_Info * +cifs_find_tcp_session(struct smb_vol *vol); + void cifs_readdata_release(struct kref *refcount); int cifs_async_readv(struct cifs_readdata *rdata); int cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid); diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index d0de4fb8ee43..5feb3bf97d70 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2295,7 +2295,7 @@ static int match_server(struct TCP_Server_Info *server, struct smb_vol *vol) return 1; } -static struct TCP_Server_Info * +struct TCP_Server_Info * cifs_find_tcp_session(struct smb_vol *vol) { struct TCP_Server_Info *server; diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c new file mode 100644 index 000000000000..1af078e531c1 --- /dev/null +++ b/fs/cifs/dfs_cache.c @@ -0,0 +1,1365 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DFS referral cache routines + * + * Copyright (c) 2018 Paulo Alcantara + */ + +#include +#include +#include +#include +#include +#include +#include +#include "cifsglob.h" +#include "smb2pdu.h" +#include "smb2proto.h" +#include "cifsproto.h" +#include "cifs_debug.h" +#include "cifs_unicode.h" +#include "smb2glob.h" + +#include "dfs_cache.h" + +#define DFS_CACHE_HTABLE_SIZE 32 +#define DFS_CACHE_MAX_ENTRIES 64 + +#define IS_INTERLINK_SET(v) ((v) & (DFSREF_REFERRAL_SERVER | \ + DFSREF_STORAGE_SERVER)) + +struct dfs_cache_tgt { + char *t_name; + struct list_head t_list; +}; + +struct dfs_cache_entry { + struct hlist_node ce_hlist; + const char *ce_path; + int ce_ttl; + int ce_srvtype; + int ce_flags; + struct timespec64 ce_etime; + int ce_path_consumed; + int ce_numtgts; + struct list_head ce_tlist; + struct dfs_cache_tgt *ce_tgthint; + struct rcu_head ce_rcu; +}; + +static struct kmem_cache *dfs_cache_slab __read_mostly; + +struct dfs_cache_vol_info { + char *vi_fullpath; + struct smb_vol vi_vol; + struct list_head vi_list; +}; + +struct dfs_cache { + struct mutex dc_lock; + struct nls_table *dc_nlsc; + struct list_head dc_vol_list; + int dc_ttl; + struct delayed_work dc_refresh; +}; + +static struct dfs_cache dfs_cache; + +/* + * Number of entries in the cache + */ +static size_t dfs_cache_count; + +static DEFINE_MUTEX(dfs_cache_list_lock); +static struct hlist_head dfs_cache_htable[DFS_CACHE_HTABLE_SIZE]; + +static void refresh_cache_worker(struct work_struct *work); + +static inline bool is_path_valid(const char *path) +{ + return path && (strchr(path + 1, '\\') || strchr(path + 1, '/')); +} + +static inline int get_normalized_path(const char *path, char **npath) +{ + if (*path == '\\') { + *npath = (char *)path; + } else { + *npath = kstrndup(path, strlen(path), GFP_KERNEL); + if (!*npath) + return -ENOMEM; + convert_delimiter(*npath, '\\'); + } + return 0; +} + +static inline void free_normalized_path(const char *path, char *npath) +{ + if (path != npath) + kfree(npath); +} + +static inline bool cache_entry_expired(const struct dfs_cache_entry *ce) +{ + struct timespec64 ts; + + ts = current_kernel_time64(); + return timespec64_compare(&ts, &ce->ce_etime) >= 0; +} + +static inline void free_tgts(struct dfs_cache_entry *ce) +{ + struct dfs_cache_tgt *t, *n; + + list_for_each_entry_safe(t, n, &ce->ce_tlist, t_list) { + list_del(&t->t_list); + kfree(t->t_name); + kfree(t); + } +} + +static void free_cache_entry(struct rcu_head *rcu) +{ + struct dfs_cache_entry *ce = container_of(rcu, struct dfs_cache_entry, + ce_rcu); + kmem_cache_free(dfs_cache_slab, ce); +} + +static inline void flush_cache_ent(struct dfs_cache_entry *ce) +{ + if (hlist_unhashed(&ce->ce_hlist)) + return; + + hlist_del_init_rcu(&ce->ce_hlist); + kfree(ce->ce_path); + free_tgts(ce); + dfs_cache_count--; + call_rcu(&ce->ce_rcu, free_cache_entry); +} + +static void flush_cache_ents(void) +{ + int i; + + rcu_read_lock(); + for (i = 0; i < DFS_CACHE_HTABLE_SIZE; i++) { + struct hlist_head *l = &dfs_cache_htable[i]; + struct dfs_cache_entry *ce; + + hlist_for_each_entry_rcu(ce, l, ce_hlist) + flush_cache_ent(ce); + } + rcu_read_unlock(); +} + +/* + * dfs cache /proc file + */ +static int dfscache_proc_show(struct seq_file *m, void *v) +{ + int bucket; + struct dfs_cache_entry *ce; + struct dfs_cache_tgt *t; + + seq_puts(m, "DFS cache\n---------\n"); + + mutex_lock(&dfs_cache_list_lock); + + rcu_read_lock(); + hash_for_each_rcu(dfs_cache_htable, bucket, ce, ce_hlist) { + seq_printf(m, + "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," + "interlink=%s,path_consumed=%d,expired=%s\n", + ce->ce_path, + ce->ce_srvtype == DFS_TYPE_ROOT ? "root" : "link", + ce->ce_ttl, ce->ce_etime.tv_nsec, + IS_INTERLINK_SET(ce->ce_flags) ? "yes" : "no", + ce->ce_path_consumed, + cache_entry_expired(ce) ? "yes" : "no"); + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + seq_printf(m, " %s%s\n", + t->t_name, + ce->ce_tgthint == t ? " (target hint)" : ""); + } + + } + rcu_read_unlock(); + + mutex_unlock(&dfs_cache_list_lock); + return 0; +} + +static ssize_t dfscache_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char c; + int rc; + + rc = get_user(c, buffer); + if (rc) + return rc; + + if (c != '0') + return -EINVAL; + + cifs_dbg(FYI, "clearing dfs cache"); + mutex_lock(&dfs_cache_list_lock); + flush_cache_ents(); + mutex_unlock(&dfs_cache_list_lock); + + return count; +} + +static int dfscache_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, dfscache_proc_show, NULL); +} + +const struct file_operations dfscache_proc_fops = { + .open = dfscache_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dfscache_proc_write, +}; + +#ifdef CONFIG_CIFS_DEBUG2 +static inline void dump_tgts(const struct dfs_cache_entry *ce) +{ + struct dfs_cache_tgt *t; + + cifs_dbg(FYI, "target list:\n"); + list_for_each_entry(t, &ce->ce_tlist, t_list) { + cifs_dbg(FYI, " %s%s\n", t->t_name, + ce->ce_tgthint == t ? " (target hint)" : ""); + } +} + +static inline void dump_ce(const struct dfs_cache_entry *ce) +{ + cifs_dbg(FYI, "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," + "interlink=%s,path_consumed=%d,expired=%s\n", ce->ce_path, + ce->ce_srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ce_ttl, + ce->ce_etime.tv_nsec, + IS_INTERLINK_SET(ce->ce_flags) ? "yes" : "no", + ce->ce_path_consumed, + cache_entry_expired(ce) ? "yes" : "no"); + dump_tgts(ce); +} + +static inline void dump_refs(const struct dfs_info3_param *refs, int numrefs) +{ + int i; + + cifs_dbg(FYI, "DFS referrals returned by the server:\n"); + for (i = 0; i < numrefs; i++) { + const struct dfs_info3_param *ref = &refs[i]; + + cifs_dbg(FYI, + "\n" + "flags: 0x%x\n" + "path_consumed: %d\n" + "server_type: 0x%x\n" + "ref_flag: 0x%x\n" + "path_name: %s\n" + "node_name: %s\n" + "ttl: %d (%dm)\n", + ref->flags, ref->path_consumed, ref->server_type, + ref->ref_flag, ref->path_name, ref->node_name, + ref->ttl, ref->ttl / 60); + } +} +#else +#define dump_tgts(e) +#define dump_ce(e) +#define dump_refs(r, n) +#endif + +/** + * dfs_cache_init - Initialize DFS referral cache. + * + * Return zero if initialized successfully, otherwise non-zero. + */ +int dfs_cache_init(void) +{ + int i; + + dfs_cache_slab = kmem_cache_create("cifs_dfs_cache", + sizeof(struct dfs_cache_entry), 0, + SLAB_HWCACHE_ALIGN, NULL); + if (!dfs_cache_slab) + return -ENOMEM; + + for (i = 0; i < DFS_CACHE_HTABLE_SIZE; i++) + INIT_HLIST_HEAD(&dfs_cache_htable[i]); + + INIT_LIST_HEAD(&dfs_cache.dc_vol_list); + mutex_init(&dfs_cache.dc_lock); + INIT_DELAYED_WORK(&dfs_cache.dc_refresh, refresh_cache_worker); + dfs_cache.dc_ttl = -1; + dfs_cache.dc_nlsc = load_nls_default(); + + cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__); + return 0; +} + +static inline unsigned int cache_entry_hash(const void *data, int size) +{ + unsigned int h; + + h = jhash(data, size, 0); + return h & (DFS_CACHE_HTABLE_SIZE - 1); +} + +/* Check whether second path component of @path is SYSVOL or NETLOGON */ +static inline bool is_sysvol_or_netlogon(const char *path) +{ + const char *s; + char sep = path[0]; + + s = strchr(path + 1, sep) + 1; + return !strncasecmp(s, "sysvol", strlen("sysvol")) || + !strncasecmp(s, "netlogon", strlen("netlogon")); +} + +/* Return target hint of a DFS cache entry */ +static inline char *get_tgt_name(const struct dfs_cache_entry *ce) +{ + struct dfs_cache_tgt *t = ce->ce_tgthint; + + return t ? t->t_name : ERR_PTR(-ENOENT); +} + +/* Return expire time out of a new entry's TTL */ +static inline struct timespec64 get_expire_time(int ttl) +{ + struct timespec64 ts = { + .tv_sec = ttl, + .tv_nsec = 0, + }; + + return timespec64_add(current_kernel_time64(), ts); +} + +/* Allocate a new DFS target */ +static inline struct dfs_cache_tgt *alloc_tgt(const char *name) +{ + struct dfs_cache_tgt *t; + + t = kmalloc(sizeof(*t), GFP_KERNEL); + if (!t) + return ERR_PTR(-ENOMEM); + t->t_name = kstrndup(name, strlen(name), GFP_KERNEL); + if (!t->t_name) { + kfree(t); + return ERR_PTR(-ENOMEM); + } + INIT_LIST_HEAD(&t->t_list); + return t; +} + +/* + * Copy DFS referral information to a cache entry and conditionally update + * target hint. + */ +static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, + struct dfs_cache_entry *ce, const char *tgthint) +{ + int i; + + ce->ce_ttl = refs[0].ttl; + ce->ce_etime = get_expire_time(ce->ce_ttl); + ce->ce_srvtype = refs[0].server_type; + ce->ce_flags = refs[0].ref_flag; + ce->ce_path_consumed = refs[0].path_consumed; + + for (i = 0; i < numrefs; i++) { + struct dfs_cache_tgt *t; + + t = alloc_tgt(refs[i].node_name); + if (IS_ERR(t)) { + free_tgts(ce); + return PTR_ERR(t); + } + if (tgthint && !strcasecmp(t->t_name, tgthint)) { + list_add(&t->t_list, &ce->ce_tlist); + tgthint = NULL; + } else { + list_add_tail(&t->t_list, &ce->ce_tlist); + } + ce->ce_numtgts++; + } + + ce->ce_tgthint = list_first_entry_or_null(&ce->ce_tlist, + struct dfs_cache_tgt, t_list); + + return 0; +} + +/* Allocate a new cache entry */ +static struct dfs_cache_entry * +alloc_cache_entry(const char *path, const struct dfs_info3_param *refs, + int numrefs) +{ + struct dfs_cache_entry *ce; + int rc; + + ce = kmem_cache_zalloc(dfs_cache_slab, GFP_KERNEL); + if (!ce) + return ERR_PTR(-ENOMEM); + + ce->ce_path = kstrdup_const(path, GFP_KERNEL); + if (!ce->ce_path) { + kfree(ce); + return ERR_PTR(-ENOMEM); + } + INIT_HLIST_NODE(&ce->ce_hlist); + INIT_LIST_HEAD(&ce->ce_tlist); + + rc = copy_ref_data(refs, numrefs, ce, NULL); + if (rc) { + kfree(ce->ce_path); + kfree(ce); + ce = ERR_PTR(rc); + } + return ce; +} + +static void remove_oldest_entry(void) +{ + int bucket; + struct dfs_cache_entry *ce; + struct dfs_cache_entry *to_del = NULL; + + rcu_read_lock(); + hash_for_each_rcu(dfs_cache_htable, bucket, ce, ce_hlist) { + if (!to_del || timespec64_compare(&ce->ce_etime, + &to_del->ce_etime) < 0) + to_del = ce; + } + if (!to_del) { + cifs_dbg(FYI, "%s: no entry to remove", __func__); + goto out; + } + cifs_dbg(FYI, "%s: removing entry", __func__); + dump_ce(to_del); + flush_cache_ent(to_del); +out: + rcu_read_unlock(); +} + +/* Add a new DFS cache entry */ +static inline struct dfs_cache_entry * +add_cache_entry(unsigned int hash, const char *path, + const struct dfs_info3_param *refs, int numrefs) +{ + struct dfs_cache_entry *ce; + + ce = alloc_cache_entry(path, refs, numrefs); + if (IS_ERR(ce)) + return ce; + + hlist_add_head_rcu(&ce->ce_hlist, &dfs_cache_htable[hash]); + + mutex_lock(&dfs_cache.dc_lock); + if (dfs_cache.dc_ttl < 0) { + dfs_cache.dc_ttl = ce->ce_ttl; + queue_delayed_work(cifsiod_wq, &dfs_cache.dc_refresh, + dfs_cache.dc_ttl * HZ); + } else { + dfs_cache.dc_ttl = min_t(int, dfs_cache.dc_ttl, ce->ce_ttl); + mod_delayed_work(cifsiod_wq, &dfs_cache.dc_refresh, + dfs_cache.dc_ttl * HZ); + } + mutex_unlock(&dfs_cache.dc_lock); + + return ce; +} + +static struct dfs_cache_entry *__find_cache_entry(unsigned int hash, + const char *path) +{ + struct dfs_cache_entry *ce; + bool found = false; + + rcu_read_lock(); + hlist_for_each_entry_rcu(ce, &dfs_cache_htable[hash], ce_hlist) { + if (!strcasecmp(path, ce->ce_path)) { +#ifdef CONFIG_CIFS_DEBUG2 + char *name = get_tgt_name(ce); + + if (unlikely(IS_ERR(name))) { + rcu_read_unlock(); + return ERR_CAST(name); + } + cifs_dbg(FYI, "%s: cache hit\n", __func__); + cifs_dbg(FYI, "%s: target hint: %s\n", __func__, name); +#endif + found = true; + break; + } + } + rcu_read_unlock(); + return found ? ce : ERR_PTR(-ENOENT); +} + +/* + * Find a DFS cache entry in hash table and optionally check prefix path against + * @path. + * Use whole path components in the match. + * Return ERR_PTR(-ENOENT) if the entry is not found. + */ +static inline struct dfs_cache_entry *find_cache_entry(const char *path, + unsigned int *hash) +{ + *hash = cache_entry_hash(path, strlen(path)); + return __find_cache_entry(*hash, path); +} + +static inline void destroy_slab_cache(void) +{ + rcu_barrier(); + kmem_cache_destroy(dfs_cache_slab); +} + +static inline void free_vol(struct dfs_cache_vol_info *vi) +{ + list_del(&vi->vi_list); + kfree(vi->vi_fullpath); + cifs_cleanup_volume_info_contents(&vi->vi_vol); + kfree(vi); +} + +static inline void free_vol_list(void) +{ + struct dfs_cache_vol_info *vi, *nvi; + + list_for_each_entry_safe(vi, nvi, &dfs_cache.dc_vol_list, vi_list) + free_vol(vi); +} + +/** + * dfs_cache_destroy - destroy DFS referral cache + */ +void dfs_cache_destroy(void) +{ + cancel_delayed_work_sync(&dfs_cache.dc_refresh); + unload_nls(dfs_cache.dc_nlsc); + free_vol_list(); + mutex_destroy(&dfs_cache.dc_lock); + + flush_cache_ents(); + destroy_slab_cache(); + mutex_destroy(&dfs_cache_list_lock); + + cifs_dbg(FYI, "%s: destroyed DFS referral cache\n", __func__); +} + +static inline struct dfs_cache_entry * +__update_cache_entry(const char *path, const struct dfs_info3_param *refs, + int numrefs) +{ + int rc; + unsigned int h; + struct dfs_cache_entry *ce; + char *s, *th = NULL; + + ce = find_cache_entry(path, &h); + if (IS_ERR(ce)) + return ce; + + if (ce->ce_tgthint) { + s = ce->ce_tgthint->t_name; + th = kstrndup(s, strlen(s), GFP_KERNEL); + if (!th) + return ERR_PTR(-ENOMEM); + } + + free_tgts(ce); + ce->ce_numtgts = 0; + + rc = copy_ref_data(refs, numrefs, ce, th); + kfree(th); + + if (rc) + ce = ERR_PTR(rc); + + return ce; +} + +/* Update an expired cache entry by getting a new DFS referral from server */ +static struct dfs_cache_entry * +update_cache_entry(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, struct dfs_cache_entry *ce) +{ + int rc; + struct dfs_info3_param *refs = NULL; + int numrefs = 0; + + cifs_dbg(FYI, "%s: update expired cache entry\n", __func__); + /* + * Check if caller provided enough parameters to update an expired + * entry. + */ + if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) + return ERR_PTR(-ETIME); + if (unlikely(!nls_codepage)) + return ERR_PTR(-ETIME); + + cifs_dbg(FYI, "%s: DFS referral request for %s\n", __func__, path); + + rc = ses->server->ops->get_dfs_refer(xid, ses, path, &refs, &numrefs, + nls_codepage, remap); + if (rc) + ce = ERR_PTR(rc); + else + ce = __update_cache_entry(path, refs, numrefs); + + dump_refs(refs, numrefs); + free_dfs_info_array(refs, numrefs); + + return ce; +} + +/* + * Find, create or update a DFS cache entry. + * + * If the entry wasn't found, it will create a new one. Or if it was found but + * expired, then it will update the entry accordingly. + * + * For interlinks, __cifs_dfs_mount() and expand_dfs_referral() are supposed to + * handle them properly. + */ +static struct dfs_cache_entry * +do_dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, bool noreq) +{ + int rc; + unsigned int h; + struct dfs_cache_entry *ce; + struct dfs_info3_param *nrefs; + int numnrefs; + + cifs_dbg(FYI, "%s: search path: %s\n", __func__, path); + + ce = find_cache_entry(path, &h); + if (IS_ERR(ce)) { + cifs_dbg(FYI, "%s: cache miss\n", __func__); + /* + * If @noreq is set, no requests will be sent to the server for + * either updating or getting a new DFS referral. + */ + if (noreq) + return ce; + /* + * No cache entry was found, so check for valid parameters that + * will be required to get a new DFS referral and then create a + * new cache entry. + */ + if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) { + ce = ERR_PTR(-EOPNOTSUPP); + return ce; + } + if (unlikely(!nls_codepage)) { + ce = ERR_PTR(-EINVAL); + return ce; + } + + nrefs = NULL; + numnrefs = 0; + + cifs_dbg(FYI, "%s: DFS referral request for %s\n", __func__, + path); + + rc = ses->server->ops->get_dfs_refer(xid, ses, path, &nrefs, + &numnrefs, nls_codepage, + remap); + if (rc) { + ce = ERR_PTR(rc); + return ce; + } + + dump_refs(nrefs, numnrefs); + + cifs_dbg(FYI, "%s: new cache entry\n", __func__); + + if (dfs_cache_count >= DFS_CACHE_MAX_ENTRIES) { + cifs_dbg(FYI, "%s: reached max cache size (%d)", + __func__, DFS_CACHE_MAX_ENTRIES); + remove_oldest_entry(); + } + ce = add_cache_entry(h, path, nrefs, numnrefs); + free_dfs_info_array(nrefs, numnrefs); + + if (IS_ERR(ce)) + return ce; + + dfs_cache_count++; + } + + dump_ce(ce); + + /* Just return the found cache entry in case @noreq is set */ + if (noreq) + return ce; + + if (cache_entry_expired(ce)) { + cifs_dbg(FYI, "%s: expired cache entry\n", __func__); + ce = update_cache_entry(xid, ses, nls_codepage, remap, path, + ce); + if (IS_ERR(ce)) { + cifs_dbg(FYI, "%s: failed to update expired entry\n", + __func__); + } + } + return ce; +} + +/* Set up a new DFS referral from a given cache entry */ +static int setup_ref(const char *path, const struct dfs_cache_entry *ce, + struct dfs_info3_param *ref, const char *tgt) +{ + int rc; + + cifs_dbg(FYI, "%s: set up new ref\n", __func__); + + memset(ref, 0, sizeof(*ref)); + + ref->path_name = kstrndup(path, strlen(path), GFP_KERNEL); + if (!ref->path_name) + return -ENOMEM; + + ref->path_consumed = ce->ce_path_consumed; + + ref->node_name = kstrndup(tgt, strlen(tgt), GFP_KERNEL); + if (!ref->node_name) { + rc = -ENOMEM; + goto err_free_path; + } + + ref->ttl = ce->ce_ttl; + ref->server_type = ce->ce_srvtype; + ref->ref_flag = ce->ce_flags; + + return 0; + +err_free_path: + kfree(ref->path_name); + ref->path_name = NULL; + return rc; +} + +/* Return target list of a DFS cache entry */ +static int get_tgt_list(const struct dfs_cache_entry *ce, + struct dfs_cache_tgt_list *tl) +{ + int rc; + struct list_head *head = &tl->tl_list; + struct dfs_cache_tgt *t; + struct dfs_cache_tgt_iterator *it, *nit; + + memset(tl, 0, sizeof(*tl)); + INIT_LIST_HEAD(head); + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + it = kzalloc(sizeof(*it), GFP_KERNEL); + if (!it) { + rc = -ENOMEM; + goto err_free_it; + } + + it->it_name = kstrndup(t->t_name, strlen(t->t_name), + GFP_KERNEL); + if (!it->it_name) { + rc = -ENOMEM; + goto err_free_it; + } + + if (ce->ce_tgthint == t) + list_add(&it->it_list, head); + else + list_add_tail(&it->it_list, head); + } + tl->tl_numtgts = ce->ce_numtgts; + + return 0; + +err_free_it: + list_for_each_entry_safe(it, nit, head, it_list) { + kfree(it->it_name); + kfree(it); + } + return rc; +} + +/** + * dfs_cache_find - find a DFS cache entry + * + * If it doesn't find the cache entry, then it will get a DFS referral + * for @path and create a new entry. + * + * In case the cache entry exists but expired, it will get a DFS referral + * for @path and then update the respective cache entry. + * + * These parameters are passed down to the get_dfs_refer() call if it + * needs to be issued: + * @xid: syscall xid + * @ses: smb session to issue the request on + * @nls_codepage: charset conversion + * @remap: path character remapping type + * @path: path to lookup in DFS referral cache. + * + * @ref: when non-NULL, store single DFS referral result in it. + * @tgt_list: when non-NULL, store complete DFS target list in it. + * + * Return zero if the target was found, otherwise non-zero. + */ +int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, struct dfs_info3_param *ref, + struct dfs_cache_tgt_list *tgt_list) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + + if (unlikely(!is_path_valid(path))) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + mutex_lock(&dfs_cache_list_lock); + ce = do_dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); + if (!IS_ERR(ce)) { + if (ref) + rc = setup_ref(path, ce, ref, get_tgt_name(ce)); + else + rc = 0; + if (!rc && tgt_list) + rc = get_tgt_list(ce, tgt_list); + } else { + rc = PTR_ERR(ce); + } + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +/** + * dfs_cache_noreq_find - find a DFS cache entry without sending any requests to + * the currently connected server. + * + * NOTE: This function will neither update a cache entry in case it was + * expired, nor create a new cache entry if @path hasn't been found. It heavily + * relies on an existing cache entry. + * + * @path: path to lookup in the DFS referral cache. + * @ref: when non-NULL, store single DFS referral result in it. + * @tgt_list: when non-NULL, store complete DFS target list in it. + * + * Return 0 if successful. + * Return -ENOENT if the entry was not found. + * Return non-zero for other errors. + */ +int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, + struct dfs_cache_tgt_list *tgt_list) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + + if (unlikely(!is_path_valid(path))) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + mutex_lock(&dfs_cache_list_lock); + ce = do_dfs_cache_find(0, NULL, NULL, 0, npath, true); + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + if (ref) + rc = setup_ref(path, ce, ref, get_tgt_name(ce)); + else + rc = 0; + if (!rc && tgt_list) + rc = get_tgt_list(ce, tgt_list); +out: + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +/** + * dfs_cache_update_tgthint - update target hint of a DFS cache entry + * + * If it doesn't find the cache entry, then it will get a DFS referral for @path + * and create a new entry. + * + * In case the cache entry exists but expired, it will get a DFS referral + * for @path and then update the respective cache entry. + * + * @xid: syscall id + * @ses: smb session + * @nls_codepage: charset conversion + * @remap: type of character remapping for paths + * @path: path to lookup in DFS referral cache. + * @it: DFS target iterator + * + * Return zero if the target hint was updated successfully, otherwise non-zero. + */ +int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, + const struct dfs_cache_tgt_iterator *it) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + struct dfs_cache_tgt *t; + + if (unlikely(!is_path_valid(path))) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); + + mutex_lock(&dfs_cache_list_lock); + ce = do_dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + rc = 0; + + t = ce->ce_tgthint; + + if (likely(!strcasecmp(it->it_name, t->t_name))) + goto out; + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + if (!strcasecmp(t->t_name, it->it_name)) { + ce->ce_tgthint = t; + cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, + it->it_name); + break; + } + } + +out: + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +/** + * dfs_cache_noreq_update_tgthint - update target hint of a DFS cache entry + * without sending any requests to the currently connected server. + * + * NOTE: This function will neither update a cache entry in case it was + * expired, nor create a new cache entry if @path hasn't been found. It heavily + * relies on an existing cache entry. + * + * @path: path to lookup in DFS referral cache. + * @it: target iterator which contains the target hint to update the cache + * entry with. + * + * Return zero if the target hint was updated successfully, otherwise non-zero. + */ +int dfs_cache_noreq_update_tgthint(const char *path, + const struct dfs_cache_tgt_iterator *it) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + struct dfs_cache_tgt *t; + + if (unlikely(!is_path_valid(path)) || !it) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); + + mutex_lock(&dfs_cache_list_lock); + + ce = do_dfs_cache_find(0, NULL, NULL, 0, npath, true); + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + rc = 0; + + t = ce->ce_tgthint; + + if (unlikely(!strcasecmp(it->it_name, t->t_name))) + goto out; + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + if (!strcasecmp(t->t_name, it->it_name)) { + ce->ce_tgthint = t; + cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, + it->it_name); + break; + } + } + +out: + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +/** + * dfs_cache_get_tgt_referral - returns a DFS referral (@ref) from a given + * target iterator (@it). + * + * @path: path to lookup in DFS referral cache. + * @it: DFS target iterator. + * @ref: DFS referral pointer to set up the gathered information. + * + * Return zero if the DFS referral was set up correctly, otherwise non-zero. + */ +int dfs_cache_get_tgt_referral(const char *path, + const struct dfs_cache_tgt_iterator *it, + struct dfs_info3_param *ref) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + unsigned int h; + + if (!it || !ref) + return -EINVAL; + if (unlikely(!is_path_valid(path))) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); + + mutex_lock(&dfs_cache_list_lock); + + ce = find_cache_entry(npath, &h); + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + cifs_dbg(FYI, "%s: target name: %s\n", __func__, it->it_name); + + rc = setup_ref(path, ce, ref, it->it_name); + +out: + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +static int dup_vol(struct smb_vol *vol, struct smb_vol *new) +{ + memcpy(new, vol, sizeof(*new)); + + if (vol->username) { + new->username = kstrndup(vol->username, strlen(vol->username), + GFP_KERNEL); + if (!new->username) + return -ENOMEM; + } + if (vol->password) { + new->password = kstrndup(vol->password, strlen(vol->password), + GFP_KERNEL); + if (!new->password) + goto err_free_username; + } + if (vol->UNC) { + cifs_dbg(FYI, "%s: vol->UNC: %s\n", __func__, vol->UNC); + new->UNC = kstrndup(vol->UNC, strlen(vol->UNC), GFP_KERNEL); + if (!new->UNC) + goto err_free_password; + } + if (vol->domainname) { + new->domainname = kstrndup(vol->domainname, + strlen(vol->domainname), GFP_KERNEL); + if (!new->domainname) + goto err_free_unc; + } + if (vol->iocharset) { + new->iocharset = kstrndup(vol->iocharset, + strlen(vol->iocharset), GFP_KERNEL); + if (!new->iocharset) + goto err_free_domainname; + } + if (vol->prepath) { + cifs_dbg(FYI, "%s: vol->prepath: %s\n", __func__, vol->prepath); + new->prepath = kstrndup(vol->prepath, strlen(vol->prepath), + GFP_KERNEL); + if (!new->prepath) + goto err_free_iocharset; + } + + return 0; + +err_free_iocharset: + kfree(new->iocharset); +err_free_domainname: + kfree(new->domainname); +err_free_unc: + kfree(new->UNC); +err_free_password: + kfree(new->password); +err_free_username: + kfree(new->username); + kfree(new); + return -ENOMEM; +} + +/** + * dfs_cache_add_vol - add a cifs volume during mount() that will be handled by + * DFS cache refresh worker. + * + * @vol: cifs volume. + * @fullpath: origin full path. + * + * Return zero if volume was set up correctly, otherwise non-zero. + */ +int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath) +{ + int rc; + struct dfs_cache_vol_info *vi; + + if (!vol || !fullpath) + return -EINVAL; + + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); + + vi = kzalloc(sizeof(*vi), GFP_KERNEL); + if (!vi) + return -ENOMEM; + + vi->vi_fullpath = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); + if (!vi->vi_fullpath) { + rc = -ENOMEM; + goto err_free_vi; + } + + rc = dup_vol(vol, &vi->vi_vol); + if (rc) + goto err_free_fullpath; + + mutex_lock(&dfs_cache.dc_lock); + list_add_tail(&vi->vi_list, &dfs_cache.dc_vol_list); + mutex_unlock(&dfs_cache.dc_lock); + return 0; + +err_free_fullpath: + kfree(vi->vi_fullpath); +err_free_vi: + kfree(vi); + return rc; +} + +static inline struct dfs_cache_vol_info *find_vol(const char *fullpath) +{ + struct dfs_cache_vol_info *vi; + + list_for_each_entry(vi, &dfs_cache.dc_vol_list, vi_list) { + cifs_dbg(FYI, "%s: vi->vi_fullpath: %s\n", __func__, + vi->vi_fullpath); + if (!strcasecmp(vi->vi_fullpath, fullpath)) + return vi; + } + return ERR_PTR(-ENOENT); +} + +/** + * dfs_cache_update_vol - update vol info in DFS cache after failover + * + * @fullpath: fullpath to look up in volume list. + * @server: TCP ses pointer. + * + * Return zero if volume was updated, otherwise non-zero. + */ +int dfs_cache_update_vol(const char *fullpath, struct TCP_Server_Info *server) +{ + int rc; + struct dfs_cache_vol_info *vi; + + if (!fullpath || !server) + return -EINVAL; + + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); + + mutex_lock(&dfs_cache.dc_lock); + + vi = find_vol(fullpath); + if (IS_ERR(vi)) { + rc = PTR_ERR(vi); + goto out; + } + + cifs_dbg(FYI, "%s: updating volume info\n", __func__); + memcpy(&vi->vi_vol.dstaddr, &server->dstaddr, + sizeof(vi->vi_vol.dstaddr)); + rc = 0; + +out: + mutex_unlock(&dfs_cache.dc_lock); + return rc; +} + +/** + * dfs_cache_del_vol - remove volume info in DFS cache during umount() + * + * @fullpath: fullpath to look up in volume list. + */ +void dfs_cache_del_vol(const char *fullpath) +{ + struct dfs_cache_vol_info *vi; + + if (!fullpath || !*fullpath) + return; + + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); + + mutex_lock(&dfs_cache.dc_lock); + vi = find_vol(fullpath); + if (!IS_ERR(vi)) + free_vol(vi); + mutex_unlock(&dfs_cache.dc_lock); +} + +/* Get all tcons that are within a DFS namespace and can be refreshed */ +static void get_tcons(struct TCP_Server_Info *server, struct list_head *head) +{ + struct cifs_ses *ses; + struct cifs_tcon *tcon; + + INIT_LIST_HEAD(head); + + spin_lock(&cifs_tcp_ses_lock); + list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { + if (!tcon->need_reconnect && !tcon->need_reopen_files && + tcon->dfs_path) { + tcon->tc_count++; + list_add_tail(&tcon->ulist, head); + } + } + if (ses->tcon_ipc && !ses->tcon_ipc->need_reconnect && + ses->tcon_ipc->dfs_path) { + list_add_tail(&ses->tcon_ipc->ulist, head); + } + } + spin_unlock(&cifs_tcp_ses_lock); +} + +/* Refresh DFS cache entry from a given tcon */ +static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon) +{ + int rc = 0; + unsigned int xid; + char *path, *npath; + unsigned int h; + struct dfs_cache_entry *ce; + struct dfs_info3_param *refs = NULL; + int numrefs = 0; + + xid = get_xid(); + + path = tcon->dfs_path + 1; + + rc = get_normalized_path(path, &npath); + if (rc) + goto out; + + mutex_lock(&dfs_cache_list_lock); + ce = find_cache_entry(npath, &h); + mutex_unlock(&dfs_cache_list_lock); + + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + if (!cache_entry_expired(ce)) + goto out; + + if (unlikely(!tcon->ses->server->ops->get_dfs_refer)) { + rc = -EOPNOTSUPP; + } else { + rc = tcon->ses->server->ops->get_dfs_refer(xid, tcon->ses, path, + &refs, &numrefs, + dc->dc_nlsc, + tcon->remap); + if (!rc) { + mutex_lock(&dfs_cache_list_lock); + ce = __update_cache_entry(npath, refs, numrefs); + mutex_unlock(&dfs_cache_list_lock); + dump_refs(refs, numrefs); + free_dfs_info_array(refs, numrefs); + if (IS_ERR(ce)) + rc = PTR_ERR(ce); + } + } + if (rc) + cifs_dbg(FYI, "%s: failed to update expired entry\n", __func__); +out: + free_xid(xid); + free_normalized_path(path, npath); +} + +/* + * Worker that will refresh DFS cache based on lowest TTL value from a DFS + * referral. + * + * FIXME: ensure that all requests are sent to DFS root for refreshing the + * cache. + */ +static void refresh_cache_worker(struct work_struct *work) +{ + struct dfs_cache *dc = container_of(work, struct dfs_cache, + dc_refresh.work); + struct dfs_cache_vol_info *vi; + struct TCP_Server_Info *server; + LIST_HEAD(list); + struct cifs_tcon *tcon, *ntcon; + + mutex_lock(&dc->dc_lock); + + list_for_each_entry(vi, &dc->dc_vol_list, vi_list) { + server = cifs_find_tcp_session(&vi->vi_vol); + if (IS_ERR_OR_NULL(server)) + continue; + if (server->tcpStatus != CifsGood) + goto next; + get_tcons(server, &list); + list_for_each_entry_safe(tcon, ntcon, &list, ulist) { + do_refresh_tcon(dc, tcon); + list_del_init(&tcon->ulist); + cifs_put_tcon(tcon); + } +next: + cifs_put_tcp_session(server, 0); + } + queue_delayed_work(cifsiod_wq, &dc->dc_refresh, dc->dc_ttl * HZ); + mutex_unlock(&dc->dc_lock); +} diff --git a/fs/cifs/dfs_cache.h b/fs/cifs/dfs_cache.h new file mode 100644 index 000000000000..22f366514f3a --- /dev/null +++ b/fs/cifs/dfs_cache.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * DFS referral cache routines + * + * Copyright (c) 2018 Paulo Alcantara + */ + +#ifndef _CIFS_DFS_CACHE_H +#define _CIFS_DFS_CACHE_H + +#include +#include +#include "cifsglob.h" + +struct dfs_cache_tgt_list { + int tl_numtgts; + struct list_head tl_list; +}; + +struct dfs_cache_tgt_iterator { + char *it_name; + struct list_head it_list; +}; + +extern int dfs_cache_init(void); +extern void dfs_cache_destroy(void); +extern const struct file_operations dfscache_proc_fops; + +extern int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, struct dfs_info3_param *ref, + struct dfs_cache_tgt_list *tgt_list); +extern int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, + struct dfs_cache_tgt_list *tgt_list); +extern int dfs_cache_update_tgthint(const unsigned int xid, + struct cifs_ses *ses, + const struct nls_table *nls_codepage, + int remap, const char *path, + const struct dfs_cache_tgt_iterator *it); +extern int +dfs_cache_noreq_update_tgthint(const char *path, + const struct dfs_cache_tgt_iterator *it); +extern int dfs_cache_get_tgt_referral(const char *path, + const struct dfs_cache_tgt_iterator *it, + struct dfs_info3_param *ref); +extern int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath); +extern int dfs_cache_update_vol(const char *fullpath, + struct TCP_Server_Info *server); +extern void dfs_cache_del_vol(const char *fullpath); + +static inline struct dfs_cache_tgt_iterator * +dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl, + struct dfs_cache_tgt_iterator *it) +{ + if (!tl || list_empty(&tl->tl_list) || !it || + list_is_last(&it->it_list, &tl->tl_list)) + return NULL; + return list_next_entry(it, it_list); +} + +static inline struct dfs_cache_tgt_iterator * +dfs_cache_get_tgt_iterator(struct dfs_cache_tgt_list *tl) +{ + if (!tl) + return NULL; + return list_first_entry_or_null(&tl->tl_list, + struct dfs_cache_tgt_iterator, + it_list); +} + +static inline void dfs_cache_free_tgts(struct dfs_cache_tgt_list *tl) +{ + struct dfs_cache_tgt_iterator *it, *nit; + + if (!tl || list_empty(&tl->tl_list)) + return; + list_for_each_entry_safe(it, nit, &tl->tl_list, it_list) { + list_del(&it->it_list); + kfree(it->it_name); + kfree(it); + } + tl->tl_numtgts = 0; +} + +static inline const char * +dfs_cache_get_tgt_name(const struct dfs_cache_tgt_iterator *it) +{ + return it ? it->it_name : NULL; +} + +static inline int +dfs_cache_get_nr_tgts(const struct dfs_cache_tgt_list *tl) +{ + return tl ? tl->tl_numtgts : 0; +} + +#endif /* _CIFS_DFS_CACHE_H */ From 54e4f73cbe03dd0634548e40d12c442d377c36c4 Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Mon, 17 Dec 2018 20:11:46 +1100 Subject: [PATCH 21/39] cifs: update for current_kernel_time64() removal Fixes cifs build failure after merge of the y2038 tree After merging the y2038 tree, today's linux-next build (x86_64 allmodconfig) failed like this: fs/cifs/dfs_cache.c: In function 'cache_entry_expired': fs/cifs/dfs_cache.c:106:7: error: implicit declaration of function 'current_kernel_time64'; did you mean 'core_kernel_text'? [-Werror=implicit-function-declaration] ts = current_kernel_time64(); ^~~~~~~~~~~~~~~~~~~~~ core_kernel_text fs/cifs/dfs_cache.c:106:5: error: incompatible types when assigning to type 'struct timespec64' from type 'int' ts = current_kernel_time64(); ^ fs/cifs/dfs_cache.c: In function 'get_expire_time': fs/cifs/dfs_cache.c:342:24: error: incompatible type for argument 1 of 'timespec64_add' return timespec64_add(current_kernel_time64(), ts); ^~~~~~~~~~~~~~~~~~~~~~~ In file included from include/linux/restart_block.h:10, from include/linux/thread_info.h:13, from arch/x86/include/asm/preempt.h:7, from include/linux/preempt.h:78, from include/linux/rcupdate.h:40, from fs/cifs/dfs_cache.c:8: include/linux/time64.h:66:66: note: expected 'struct timespec64' but argument is of type 'int' static inline struct timespec64 timespec64_add(struct timespec64 lhs, ~~~~~~~~~~~~~~~~~~^~~ fs/cifs/dfs_cache.c:343:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ Caused by: commit ccea641b6742 ("timekeeping: remove obsolete time accessors") interacting with: commit 34a44fb160f9 ("cifs: Add DFS cache routines") from the cifs tree. Signed-off-by: Stephen Rothwell Reviewed-by: Paulo Alcantara Acked-by: Arnd Bergmann Signed-off-by: Steve French --- fs/cifs/dfs_cache.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c index 1af078e531c1..d1e84bd3268f 100644 --- a/fs/cifs/dfs_cache.c +++ b/fs/cifs/dfs_cache.c @@ -103,7 +103,7 @@ static inline bool cache_entry_expired(const struct dfs_cache_entry *ce) { struct timespec64 ts; - ts = current_kernel_time64(); + ktime_get_coarse_real_ts64(&ts); return timespec64_compare(&ts, &ce->ce_etime) >= 0; } @@ -338,8 +338,10 @@ static inline struct timespec64 get_expire_time(int ttl) .tv_sec = ttl, .tv_nsec = 0, }; + struct timespec64 now; - return timespec64_add(current_kernel_time64(), ts); + ktime_get_coarse_real_ts64(&now); + return timespec64_add(now, ts); } /* Allocate a new DFS target */ From 3e80be0158ac67d936e65935db5b07663822c66d Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Tue, 18 Dec 2018 06:37:02 +0000 Subject: [PATCH 22/39] cifs: Fix to use kmem_cache_free() instead of kfree() memory allocated by kmem_cache_alloc() in alloc_cache_entry() should be freed using kmem_cache_free(), not kfree(). Fixes: 34a44fb160f9 ("cifs: Add DFS cache routines") Signed-off-by: Wei Yongjun Signed-off-by: Steve French Reviewed-by: Aurelien Aptel --- fs/cifs/dfs_cache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c index d1e84bd3268f..f4e09073bb7d 100644 --- a/fs/cifs/dfs_cache.c +++ b/fs/cifs/dfs_cache.c @@ -413,7 +413,7 @@ alloc_cache_entry(const char *path, const struct dfs_info3_param *refs, ce->ce_path = kstrdup_const(path, GFP_KERNEL); if (!ce->ce_path) { - kfree(ce); + kmem_cache_free(dfs_cache_slab, ce); return ERR_PTR(-ENOMEM); } INIT_HLIST_NODE(&ce->ce_hlist); @@ -422,7 +422,7 @@ alloc_cache_entry(const char *path, const struct dfs_info3_param *refs, rc = copy_ref_data(refs, numrefs, ce, NULL); if (rc) { kfree(ce->ce_path); - kfree(ce); + kmem_cache_free(dfs_cache_slab, ce); ce = ERR_PTR(rc); } return ce; From 34bca9bbe7a81c4b53cacb20e996090ddf371c51 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 20 Dec 2018 14:32:43 +0300 Subject: [PATCH 23/39] cifs: Use kzfree() to free password We should zero out the password before we free it. Fixes: 3d6cacbb5310 ("cifs: Add DFS cache routines") Signed-off-by: Dan Carpenter Signed-off-by: Steve French Reviewed-by: Paulo Alcantara --- fs/cifs/dfs_cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c index f4e09073bb7d..cd63c4a70875 100644 --- a/fs/cifs/dfs_cache.c +++ b/fs/cifs/dfs_cache.c @@ -1127,7 +1127,7 @@ err_free_domainname: err_free_unc: kfree(new->UNC); err_free_password: - kfree(new->password); + kzfree(new->password); err_free_username: kfree(new->username); kfree(new); From 29cbfa1b2be2e51785f871351d321896861f2ce8 Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 18 Dec 2018 02:51:51 +0000 Subject: [PATCH 24/39] cifs: remove set but not used variable 'server' Fixes gcc '-Wunused-but-set-variable' warning: fs/cifs/smb2pdu.c: In function 'smb311_posix_mkdir': fs/cifs/smb2pdu.c:2040:26: warning: variable 'server' set but not used [-Wunused-but-set-variable] fs/cifs/smb2pdu.c: In function 'build_qfs_info_req': fs/cifs/smb2pdu.c:4067:26: warning: variable 'server' set but not used [-Wunused-but-set-variable] The first 'server' never used since commit bea851b8babe ("smb3: Fix mode on mkdir on smb311 mounts") And the second not used since commit 1fc6ad2f10ad ("cifs: remove header_preamble_size where it is always 0") Signed-off-by: YueHaibing Signed-off-by: Steve French --- fs/cifs/smb2pdu.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 448031898dd4..67ce3399400b 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -1955,7 +1955,6 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode, struct smb_rqst rqst; struct smb2_create_req *req; struct smb2_create_rsp *rsp = NULL; - struct TCP_Server_Info *server; struct cifs_ses *ses = tcon->ses; struct kvec iov[3]; /* make sure at least one for each open context */ struct kvec rsp_iov = {NULL, 0}; @@ -1978,9 +1977,7 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode, if (!utf16_path) return -ENOMEM; - if (ses && (ses->server)) - server = ses->server; - else { + if (!ses || !(ses->server)) { rc = -EIO; goto err_free_path; } @@ -3982,7 +3979,6 @@ static int build_qfs_info_req(struct kvec *iov, struct cifs_tcon *tcon, int level, int outbuf_len, u64 persistent_fid, u64 volatile_fid) { - struct TCP_Server_Info *server; int rc; struct smb2_query_info_req *req; unsigned int total_len; @@ -3992,8 +3988,6 @@ build_qfs_info_req(struct kvec *iov, struct cifs_tcon *tcon, int level, if ((tcon->ses == NULL) || (tcon->ses->server == NULL)) return -EIO; - server = tcon->ses->server; - rc = smb2_plain_req_init(SMB2_QUERY_INFO, tcon, (void **) &req, &total_len); if (rc) From 0544b324e62c177c3a9e9c3bdce22e6db9f34588 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Thu, 20 Dec 2018 23:50:48 -0600 Subject: [PATCH 25/39] cifs: check kzalloc return kzalloc can return NULL so an additional check is needed. While there is a check for ret_buf there is no check for the allocation of ret_buf->crfid.fid - this check is thus added. Both call-sites of tconInfoAlloc() check for NULL return of tconInfoAlloc() so returning NULL on failure of kzalloc() here seems appropriate. As the kzalloc() is the only thing here that can fail it is moved to the beginning so as not to initialize other resources on failure of kzalloc. Fixes: 3d4ef9a15343 ("smb3: fix redundant opens on root") Signed-off-by: Joe Perches Signed-off-by: Steve French --- fs/cifs/misc.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index 5e315e4009e2..10ae1a35b6f7 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -111,21 +111,27 @@ struct cifs_tcon * tconInfoAlloc(void) { struct cifs_tcon *ret_buf; - ret_buf = kzalloc(sizeof(struct cifs_tcon), GFP_KERNEL); - if (ret_buf) { - atomic_inc(&tconInfoAllocCount); - ret_buf->tidStatus = CifsNew; - ++ret_buf->tc_count; - INIT_LIST_HEAD(&ret_buf->openFileList); - INIT_LIST_HEAD(&ret_buf->tcon_list); - spin_lock_init(&ret_buf->open_file_lock); - mutex_init(&ret_buf->crfid.fid_mutex); - ret_buf->crfid.fid = kzalloc(sizeof(struct cifs_fid), - GFP_KERNEL); - spin_lock_init(&ret_buf->stat_lock); - atomic_set(&ret_buf->num_local_opens, 0); - atomic_set(&ret_buf->num_remote_opens, 0); + + ret_buf = kzalloc(sizeof(*ret_buf), GFP_KERNEL); + if (!ret_buf) + return NULL; + ret_buf->crfid.fid = kzalloc(sizeof(*ret_buf->crfid.fid), GFP_KERNEL); + if (!ret_buf->crfid.fid) { + kfree(ret_buf); + return NULL; } + + atomic_inc(&tconInfoAllocCount); + ret_buf->tidStatus = CifsNew; + ++ret_buf->tc_count; + INIT_LIST_HEAD(&ret_buf->openFileList); + INIT_LIST_HEAD(&ret_buf->tcon_list); + spin_lock_init(&ret_buf->open_file_lock); + mutex_init(&ret_buf->crfid.fid_mutex); + spin_lock_init(&ret_buf->stat_lock); + atomic_set(&ret_buf->num_local_opens, 0); + atomic_set(&ret_buf->num_remote_opens, 0); + return ret_buf; } From e8bcdfdbf91bf979afe6642a99b41cbc0a4916d0 Mon Sep 17 00:00:00 2001 From: Steve French Date: Fri, 21 Dec 2018 00:44:32 -0600 Subject: [PATCH 26/39] cifs: minor updates to documentation Update cifs "TODO" file. Signed-off-by: Steve French --- Documentation/filesystems/cifs/TODO | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Documentation/filesystems/cifs/TODO b/Documentation/filesystems/cifs/TODO index 852499aed64b..66b3f54aa6dc 100644 --- a/Documentation/filesystems/cifs/TODO +++ b/Documentation/filesystems/cifs/TODO @@ -1,4 +1,4 @@ -Version 2.11 September 13, 2017 +Version 2.14 December 21, 2018 A Partial List of Missing Features ================================== @@ -7,7 +7,7 @@ Contributions are welcome. There are plenty of opportunities for visible, important contributions to this module. Here is a partial list of the known problems and missing features: -a) SMB3 (and SMB3.02) missing optional features: +a) SMB3 (and SMB3.1.1) missing optional features: - multichannel (started), integration with RDMA - directory leases (improved metadata caching), started (root dir only) - T10 copy offload ie "ODX" (copy chunk, and "Duplicate Extents" ioctl @@ -21,8 +21,9 @@ using Directory Leases, currently only the root file handle is cached longer d) quota support (needs minor kernel change since quota calls to make it to network filesystems or deviceless filesystems) -e) Compounding (in progress) to reduce number of roundtrips, and also -better optimize open to reduce redundant opens (using reference counts more). +e) Additional use cases where we use "compoounding" (e.g. open/query/close +and open/setinfo/close) to reduce the number of roundtrips, and also +open to reduce redundant opens (using deferred close and reference counts more). f) Finish inotify support so kde and gnome file list windows will autorefresh (partially complete by Asser). Needs minor kernel @@ -43,11 +44,13 @@ exists. Also better integration with winbind for resolving SID owners k) Add tools to take advantage of more smb3 specific ioctls and features (passthrough ioctl/fsctl for sending various SMB3 fsctls to the server -is in progress) +is in progress, and a passthrough query_info call is already implemented +in cifs.ko to allow smb3 info levels queries to be sent from userspace) l) encrypted file support -m) improved stats gathering, tools (perhaps integration with nfsometer?) +m) improved stats gathering tools (perhaps integration with nfsometer?) +to extend and make easier to use what is currently in /proc/fs/cifs/Stats n) allow setting more NTFS/SMB3 file attributes remotely (currently limited to compressed file attribute via chflags) and improve user space tools for managing and @@ -76,6 +79,9 @@ and simplify the code. v) POSIX Extensions for SMB3.1.1 (started, create and mkdir support added so far). +w) Add support for additional strong encryption types, and additional spnego +authentication mechanisms (see MS-SMB2) + KNOWN BUGS ==================================== See http://bugzilla.samba.org - search on product "CifsVFS" for @@ -102,3 +108,11 @@ and when signing is disabled to request larger read sizes (larger than negotiated size) and send larger write sizes to modern servers. 4) More exhaustively test against less common servers + +5) Continue to extend the smb3 "buildbot" which does automated xfstesting +against Windows, Samba and Azure currently - to add additional tests and +to allow the buildbot to execute the tests faster. + +6) Address various coverity warnings (most are not bugs per-se, but +the more warnings are addressed, the easier it is to spot real +problems that static analyzers will point out in the future). From 1c780228e9d47b966983a8bbf475dd45967bf0d5 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 16:24:03 -0200 Subject: [PATCH 27/39] cifs: Make use of DFS cache to get new DFS referrals This patch will make use of DFS cache routines where appropriate and do not always request a new referral from server. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifs_dfs_ref.c | 106 +++++++++++++++++++++++++---------------- fs/cifs/cifsfs.c | 17 ++++++- fs/cifs/cifsglob.h | 1 - fs/cifs/cifsproto.h | 19 ++++++-- fs/cifs/connect.c | 45 ++++++----------- fs/cifs/smb1ops.c | 15 +++--- 6 files changed, 116 insertions(+), 87 deletions(-) diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c index 7adbdf9eb137..6e6953f35db2 100644 --- a/fs/cifs/cifs_dfs_ref.c +++ b/fs/cifs/cifs_dfs_ref.c @@ -25,6 +25,7 @@ #include "dns_resolve.h" #include "cifs_debug.h" #include "cifs_unicode.h" +#include "dfs_cache.h" static LIST_HEAD(cifs_dfs_automount_list); @@ -285,16 +286,16 @@ static void dump_referral(const struct dfs_info3_param *ref) */ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) { - struct dfs_info3_param *referrals = NULL; - unsigned int num_referrals = 0; + struct dfs_info3_param referral = {0}; struct cifs_sb_info *cifs_sb; struct cifs_ses *ses; - char *full_path; + struct cifs_tcon *tcon; + char *full_path, *root_path; unsigned int xid; - int i; + int len; int rc; struct vfsmount *mnt; - struct tcon_link *tlink; + char sep; cifs_dbg(FYI, "in %s\n", __func__); BUG_ON(IS_ROOT(mntpt)); @@ -313,53 +314,76 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) goto cdda_exit; } + sep = CIFS_DIR_SEP(cifs_sb); + /* always use tree name prefix */ full_path = build_path_from_dentry_optional_prefix(mntpt, true); if (full_path == NULL) goto cdda_exit; - tlink = cifs_sb_tlink(cifs_sb); - if (IS_ERR(tlink)) { - mnt = ERR_CAST(tlink); + cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); + + if (!cifs_sb_master_tlink(cifs_sb)) { + cifs_dbg(FYI, "%s: master tlink is NULL\n", __func__); goto free_full_path; } - ses = tlink_tcon(tlink)->ses; - xid = get_xid(); - rc = get_dfs_path(xid, ses, full_path + 1, cifs_sb->local_nls, - &num_referrals, &referrals, - cifs_remap(cifs_sb)); - free_xid(xid); - - cifs_put_tlink(tlink); - - mnt = ERR_PTR(-ENOENT); - for (i = 0; i < num_referrals; i++) { - int len; - dump_referral(referrals + i); - /* connect to a node */ - len = strlen(referrals[i].node_name); - if (len < 2) { - cifs_dbg(VFS, "%s: Net Address path too short: %s\n", - __func__, referrals[i].node_name); - mnt = ERR_PTR(-EINVAL); - break; - } - mnt = cifs_dfs_do_refmount(mntpt, cifs_sb, - full_path, referrals + i); - cifs_dbg(FYI, "%s: cifs_dfs_do_refmount:%s , mnt:%p\n", - __func__, referrals[i].node_name, mnt); - if (!IS_ERR(mnt)) - goto success; + tcon = cifs_sb_master_tcon(cifs_sb); + if (!tcon) { + cifs_dbg(FYI, "%s: master tcon is NULL\n", __func__); + goto free_full_path; } - /* no valid submounts were found; return error from get_dfs_path() by - * preference */ - if (rc != 0) - mnt = ERR_PTR(rc); + root_path = kstrdup(tcon->treeName, GFP_KERNEL); + if (!root_path) { + mnt = ERR_PTR(-ENOMEM); + goto free_full_path; + } + cifs_dbg(FYI, "%s: root path: %s\n", __func__, root_path); -success: - free_dfs_info_array(referrals, num_referrals); + ses = tcon->ses; + xid = get_xid(); + + /* + * If DFS root has been expired, then unconditionally fetch it again to + * refresh DFS referral cache. + */ + rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), + root_path + 1, NULL, NULL); + if (!rc) { + rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, + cifs_remap(cifs_sb), full_path + 1, + &referral, NULL); + } + + free_xid(xid); + + if (rc) { + mnt = ERR_PTR(rc); + goto free_root_path; + } + + dump_referral(&referral); + + len = strlen(referral.node_name); + if (len < 2) { + cifs_dbg(VFS, "%s: Net Address path too short: %s\n", + __func__, referral.node_name); + mnt = ERR_PTR(-EINVAL); + goto free_dfs_ref; + } + /* + * cifs_mount() will retry every available node server in case + * of failures. + */ + mnt = cifs_dfs_do_refmount(mntpt, cifs_sb, full_path, &referral); + cifs_dbg(FYI, "%s: cifs_dfs_do_refmount:%s , mnt:%p\n", __func__, + referral.node_name, mnt); + +free_dfs_ref: + free_dfs_info_param(&referral); +free_root_path: + kfree(root_path); free_full_path: kfree(full_path); cdda_exit: diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 865706edb307..62d48d486d8f 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -52,6 +52,9 @@ #include "cifs_spnego.h" #include "fscache.h" #include "smb2pdu.h" +#ifdef CONFIG_CIFS_DFS_UPCALL +#include "dfs_cache.h" +#endif int cifsFYI = 0; bool traceSMB; @@ -1494,10 +1497,15 @@ init_cifs(void) if (rc) goto out_destroy_mids; +#ifdef CONFIG_CIFS_DFS_UPCALL + rc = dfs_cache_init(); + if (rc) + goto out_destroy_request_bufs; +#endif /* CONFIG_CIFS_DFS_UPCALL */ #ifdef CONFIG_CIFS_UPCALL rc = init_cifs_spnego(); if (rc) - goto out_destroy_request_bufs; + goto out_destroy_dfs_cache; #endif /* CONFIG_CIFS_UPCALL */ #ifdef CONFIG_CIFS_ACL @@ -1525,6 +1533,10 @@ out_register_key_type: #endif #ifdef CONFIG_CIFS_UPCALL exit_cifs_spnego(); +out_destroy_dfs_cache: +#endif +#ifdef CONFIG_CIFS_DFS_UPCALL + dfs_cache_destroy(); out_destroy_request_bufs: #endif cifs_destroy_request_bufs(); @@ -1555,6 +1567,9 @@ exit_cifs(void) #endif #ifdef CONFIG_CIFS_UPCALL exit_cifs_spnego(); +#endif +#ifdef CONFIG_CIFS_DFS_UPCALL + dfs_cache_destroy(); #endif cifs_destroy_request_bufs(); cifs_destroy_mids(); diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 66c487634c9e..45feb3ff7806 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1551,7 +1551,6 @@ static inline void free_dfs_info_param(struct dfs_info3_param *param) if (param) { kfree(param->path_name); kfree(param->node_name); - kfree(param); } } diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index efa5e36b3762..f277bc5a0c4e 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -22,6 +22,9 @@ #define _CIFSPROTO_H #include #include "trace.h" +#ifdef CONFIG_CIFS_DFS_UPCALL +#include "dfs_cache.h" +#endif struct statfs; struct smb_vol; @@ -294,11 +297,6 @@ extern int CIFSGetDFSRefer(const unsigned int xid, struct cifs_ses *ses, unsigned int *num_of_nodes, const struct nls_table *nls_codepage, int remap); -extern int get_dfs_path(const unsigned int xid, struct cifs_ses *ses, - const char *old_path, - const struct nls_table *nls_codepage, - unsigned int *num_referrals, - struct dfs_info3_param **referrals, int remap); extern int parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, unsigned int *num_of_nodes, struct dfs_info3_param **target_nodes, @@ -567,4 +565,15 @@ void cifs_free_hash(struct crypto_shash **shash, struct sdesc **sdesc); extern void rqst_page_get_length(struct smb_rqst *rqst, unsigned int page, unsigned int *len, unsigned int *offset); +#ifdef CONFIG_CIFS_DFS_UPCALL +static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses, + const char *old_path, + const struct nls_table *nls_codepage, + struct dfs_info3_param *referral, int remap) +{ + return dfs_cache_find(xid, ses, nls_codepage, remap, old_path, + referral, NULL); +} +#endif + #endif /* _CIFSPROTO_H */ diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 5feb3bf97d70..182b16e56749 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -56,6 +56,10 @@ #include "fscache.h" #include "smb2proto.h" #include "smbdirect.h" +#include "dns_resolve.h" +#ifdef CONFIG_CIFS_DFS_UPCALL +#include "dfs_cache.h" +#endif extern mempool_t *cifs_req_poolp; extern bool disable_legacy_dialects; @@ -3262,25 +3266,6 @@ out: return rc; } -int -get_dfs_path(const unsigned int xid, struct cifs_ses *ses, const char *old_path, - const struct nls_table *nls_codepage, unsigned int *num_referrals, - struct dfs_info3_param **referrals, int remap) -{ - int rc = 0; - - if (!ses->server->ops->get_dfs_refer) - return -ENOSYS; - - *num_referrals = 0; - *referrals = NULL; - - rc = ses->server->ops->get_dfs_refer(xid, ses, old_path, - referrals, num_referrals, - nls_codepage, remap); - return rc; -} - #ifdef CONFIG_DEBUG_LOCK_ALLOC static struct lock_class_key cifs_key[2]; static struct lock_class_key cifs_slock_key[2]; @@ -3931,8 +3916,9 @@ build_unc_path_to_root(const struct smb_vol *vol, return full_path; } -/* - * Perform a dfs referral query for a share and (optionally) prefix +/** + * expand_dfs_referral - Perform a dfs referral query and update the cifs_sb + * * * If a referral is found, cifs_sb->mountdata will be (re-)allocated * to a string containing updated options for the submount. Otherwise it @@ -3947,8 +3933,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, int check_prefix) { int rc; - unsigned int num_referrals = 0; - struct dfs_info3_param *referrals = NULL; + struct dfs_info3_param referral = {0}; char *full_path = NULL, *ref_path = NULL, *mdata = NULL; if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) @@ -3961,17 +3946,15 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, /* For DFS paths, skip the first '\' of the UNC */ ref_path = check_prefix ? full_path + 1 : volume_info->UNC + 1; - rc = get_dfs_path(xid, ses, ref_path, cifs_sb->local_nls, - &num_referrals, &referrals, cifs_remap(cifs_sb)); - - if (!rc && num_referrals > 0) { + rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), + ref_path, &referral, NULL); + if (!rc) { char *fake_devname = NULL; mdata = cifs_compose_mount_options(cifs_sb->mountdata, - full_path + 1, referrals, + full_path + 1, &referral, &fake_devname); - - free_dfs_info_array(referrals, num_referrals); + free_dfs_info_param(&referral); if (IS_ERR(mdata)) { rc = PTR_ERR(mdata); @@ -3979,7 +3962,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, } else { cifs_cleanup_volume_info_contents(volume_info); rc = cifs_setup_volume_info(volume_info, mdata, - fake_devname, false); + fake_devname, false); } kfree(fake_devname); kfree(cifs_sb->mountdata); diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c index 378151e09e91..32a6c020478f 100644 --- a/fs/cifs/smb1ops.c +++ b/fs/cifs/smb1ops.c @@ -929,19 +929,18 @@ cifs_unix_dfs_readlink(const unsigned int xid, struct cifs_tcon *tcon, { #ifdef CONFIG_CIFS_DFS_UPCALL int rc; - unsigned int num_referrals = 0; - struct dfs_info3_param *referrals = NULL; + struct dfs_info3_param referral = {0}; - rc = get_dfs_path(xid, tcon->ses, searchName, nls_codepage, - &num_referrals, &referrals, 0); + rc = get_dfs_path(xid, tcon->ses, searchName, nls_codepage, &referral, + 0); - if (!rc && num_referrals > 0) { - *symlinkinfo = kstrndup(referrals->node_name, - strlen(referrals->node_name), + if (!rc) { + *symlinkinfo = kstrndup(referral.node_name, + strlen(referral.node_name), GFP_KERNEL); + free_dfs_info_param(&referral); if (!*symlinkinfo) rc = -ENOMEM; - free_dfs_info_array(referrals, num_referrals); } return rc; #else /* No DFS support */ From 5a650501eb8cb785593155441c5b0b9900edfcdf Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 18 Dec 2018 01:35:29 +0000 Subject: [PATCH 28/39] cifs: remove set but not used variable 'sep' Fixes gcc '-Wunused-but-set-variable' warning: fs/cifs/cifs_dfs_ref.c: In function 'cifs_dfs_do_automount': fs/cifs/cifs_dfs_ref.c:309:7: warning: variable 'sep' set but not used [-Wunused-but-set-variable] It never used since introdution in commit 0f56b277073c ("cifs: Make use of DFS cache to get new DFS referrals") Signed-off-by: YueHaibing Reviewed-by: Paulo Alcantara Signed-off-by: Steve French --- fs/cifs/cifs_dfs_ref.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c index 6e6953f35db2..8bd6f6b76da8 100644 --- a/fs/cifs/cifs_dfs_ref.c +++ b/fs/cifs/cifs_dfs_ref.c @@ -295,7 +295,6 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) int len; int rc; struct vfsmount *mnt; - char sep; cifs_dbg(FYI, "in %s\n", __func__); BUG_ON(IS_ROOT(mntpt)); @@ -314,8 +313,6 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) goto cdda_exit; } - sep = CIFS_DIR_SEP(cifs_sb); - /* always use tree name prefix */ full_path = build_path_from_dentry_optional_prefix(mntpt, true); if (full_path == NULL) From 4a367dc0443566f87d73f2cdb94703b0e1374315 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 16:53:52 -0200 Subject: [PATCH 29/39] cifs: Add support for failover in cifs_mount() This patch adds support for failover when failing to connect in cifs_mount(). Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifs_dfs_ref.c | 20 +++- fs/cifs/connect.c | 228 +++++++++++++++++++++++++++++++++++++++-- fs/cifs/misc.c | 3 + 3 files changed, 236 insertions(+), 15 deletions(-) diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c index 8bd6f6b76da8..d9b99abe1243 100644 --- a/fs/cifs/cifs_dfs_ref.c +++ b/fs/cifs/cifs_dfs_ref.c @@ -255,20 +255,30 @@ static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt, { struct vfsmount *mnt; char *mountdata; - char *devname = NULL; + char *devname; + + /* + * Always pass down the DFS full path to smb3_do_mount() so we + * can use it later for failover. + */ + devname = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); + if (!devname) + return ERR_PTR(-ENOMEM); + + convert_delimiter(devname, '/'); /* strip first '\' from fullpath */ mountdata = cifs_compose_mount_options(cifs_sb->mountdata, - fullpath + 1, ref, &devname); - - if (IS_ERR(mountdata)) + fullpath + 1, ref, NULL); + if (IS_ERR(mountdata)) { + kfree(devname); return (struct vfsmount *)mountdata; + } mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata); kfree(mountdata); kfree(devname); return mnt; - } static void dump_referral(const struct dfs_info3_param *ref) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 182b16e56749..658a0d191056 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3891,10 +3891,11 @@ static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, */ static char * build_unc_path_to_root(const struct smb_vol *vol, - const struct cifs_sb_info *cifs_sb) + const struct cifs_sb_info *cifs_sb, bool useppath) { char *full_path, *pos; - unsigned int pplen = vol->prepath ? strlen(vol->prepath) + 1 : 0; + unsigned int pplen = useppath && vol->prepath ? + strlen(vol->prepath) + 1 : 0; unsigned int unc_len = strnlen(vol->UNC, MAX_TREE_SIZE + 1); full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL); @@ -3939,7 +3940,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) return -EREMOTE; - full_path = build_unc_path_to_root(volume_info, cifs_sb); + full_path = build_unc_path_to_root(volume_info, cifs_sb, true); if (IS_ERR(full_path)) return PTR_ERR(full_path); @@ -3971,6 +3972,143 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, kfree(full_path); return rc; } + +static inline int get_next_dfs_tgt(const char *path, + struct dfs_cache_tgt_list *tgt_list, + struct dfs_cache_tgt_iterator **tgt_it) +{ + if (!*tgt_it) + *tgt_it = dfs_cache_get_tgt_iterator(tgt_list); + else + *tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it); + return !*tgt_it ? -EHOSTDOWN : 0; +} + +static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it, + struct smb_vol *fake_vol, struct smb_vol *vol) +{ + const char *tgt = dfs_cache_get_tgt_name(tgt_it); + int len = strlen(tgt) + 2; + char *new_unc; + + new_unc = kmalloc(len, GFP_KERNEL); + if (!new_unc) + return -ENOMEM; + snprintf(new_unc, len, "\\%s", tgt); + + kfree(vol->UNC); + vol->UNC = new_unc; + + if (fake_vol->prepath) { + kfree(vol->prepath); + vol->prepath = fake_vol->prepath; + fake_vol->prepath = NULL; + } + memcpy(&vol->dstaddr, &fake_vol->dstaddr, sizeof(vol->dstaddr)); + + return 0; +} + +static int setup_dfs_tgt_conn(const char *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_tcon **tcon) +{ + int rc; + struct dfs_info3_param ref = {0}; + char *mdata = NULL, *fake_devname = NULL; + struct smb_vol fake_vol = {0}; + + cifs_dbg(FYI, "%s: dfs path: %s\n", __func__, path); + + rc = dfs_cache_get_tgt_referral(path, tgt_it, &ref); + if (rc) + return rc; + + mdata = cifs_compose_mount_options(cifs_sb->mountdata, path, &ref, + &fake_devname); + free_dfs_info_param(&ref); + + if (IS_ERR(mdata)) { + rc = PTR_ERR(mdata); + mdata = NULL; + } else { + cifs_dbg(FYI, "%s: fake_devname: %s\n", __func__, fake_devname); + rc = cifs_setup_volume_info(&fake_vol, mdata, fake_devname, + false); + } + kfree(mdata); + kfree(fake_devname); + + if (!rc) { + /* + * We use a 'fake_vol' here because we need pass it down to the + * mount_{get,put} functions to test connection against new DFS + * targets. + */ + mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon); + rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses, + tcon); + if (!rc) { + /* + * We were able to connect to new target server. + * Update current volume info with new target server. + */ + rc = update_vol_info(tgt_it, &fake_vol, vol); + } + } + cifs_cleanup_volume_info_contents(&fake_vol); + 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) +{ + int rc; + struct dfs_cache_tgt_list tgt_list; + struct dfs_cache_tgt_iterator *tgt_it = NULL; + + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) + return -EOPNOTSUPP; + + rc = dfs_cache_noreq_find(path, NULL, &tgt_list); + if (rc) + return rc; + + for (;;) { + /* Get next DFS target server - if any */ + rc = get_next_dfs_tgt(path, &tgt_list, &tgt_it); + 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) + break; + } + if (!rc) { + /* + * Update DFS target hint in DFS referral cache with the target + * server we successfully reconnected to. + */ + rc = dfs_cache_update_tgthint(*xid, root_ses ? root_ses : *ses, + cifs_sb->local_nls, + cifs_remap(cifs_sb), path, + tgt_it); + } + dfs_cache_free_tgts(&tgt_list); + return rc; +} #endif static int @@ -4123,22 +4261,47 @@ 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 cifs_tcon *tcon = NULL; struct TCP_Server_Info *server; + char *root_path = NULL, *full_path = NULL; char *old_mountdata; int count; rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); if (!rc && tcon) { - rc = is_path_remote(cifs_sb, vol, xid, server, tcon); - if (!rc) - goto out; - if (rc != -EREMOTE) - goto error; + /* 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 ((rc == -EACCES) || (rc == -EOPNOTSUPP) || (ses == NULL) || (server == NULL)) + /* + * 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. + * + * 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. + */ + 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; + } /* * Perform an unconditional check for whether there are DFS @@ -4163,8 +4326,36 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) 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; + 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); + /* + * Save root tcon for additional DFS requests to update or create a new + * DFS cache entry, or even perform DFS failover. + */ + spin_lock(&cifs_tcp_ses_lock); + tcon->tc_count++; + tcon->dfs_path = root_path; + root_path = NULL; + tcon->remap = cifs_remap(cifs_sb); + spin_unlock(&cifs_tcp_ses_lock); + + root_tcon = tcon; + for (count = 1; ;) { if (!rc && tcon) { rc = is_path_remote(cifs_sb, vol, xid, server, tcon); @@ -4182,8 +4373,16 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) break; } + kfree(full_path); + full_path = build_unc_path_to_root(vol, cifs_sb, true); + if (IS_ERR(full_path)) { + rc = PTR_ERR(full_path); + full_path = NULL; + break; + } + old_mountdata = cifs_sb->mountdata; - rc = expand_dfs_referral(xid, tcon->ses, vol, cifs_sb, + rc = expand_dfs_referral(xid, root_tcon->ses, vol, cifs_sb, true); if (rc) break; @@ -4194,11 +4393,18 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) &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; } } + cifs_put_tcon(root_tcon); if (rc) goto error; @@ -4214,6 +4420,8 @@ out: return mount_setup_tlink(cifs_sb, ses, tcon); error: + kfree(full_path); + kfree(root_path); mount_put_conns(cifs_sb, xid, server, ses, tcon); return rc; } diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index 10ae1a35b6f7..7c858d4c66f3 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -146,6 +146,9 @@ tconInfoFree(struct cifs_tcon *buf_to_free) kfree(buf_to_free->nativeFileSystem); kzfree(buf_to_free->password); kfree(buf_to_free->crfid.fid); +#ifdef CONFIG_CIFS_DFS_UPCALL + kfree(buf_to_free->dfs_path); +#endif kfree(buf_to_free); } From 93d5cb517db39e8af8d1292f9e785e4983b7f708 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 17:13:25 -0200 Subject: [PATCH 30/39] cifs: Add support for failover in cifs_reconnect() After failing to reconnect to original target, it will retry any target available from DFS cache. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifs_fs_sb.h | 9 +++ fs/cifs/cifsglob.h | 7 ++ fs/cifs/connect.c | 189 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 204 insertions(+), 1 deletion(-) diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h index 63d7530f2e1d..42f0d67f1054 100644 --- a/fs/cifs/cifs_fs_sb.h +++ b/fs/cifs/cifs_fs_sb.h @@ -72,6 +72,15 @@ struct cifs_sb_info { char *mountdata; /* options received at mount time or via DFS refs */ struct delayed_work prune_tlinks; struct rcu_head rcu; + + /* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */ char *prepath; + + /* + * Path initially provided by the mount call. We might connect + * to something different via DFS but we want to keep it to do + * failover properly. + */ + char *origin_fullpath; /* \\HOST\SHARE\[OPTIONAL PATH] */ }; #endif /* _CIFS_FS_SB_H */ diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 45feb3ff7806..01ded7038b19 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -701,6 +701,13 @@ struct TCP_Server_Info { struct delayed_work reconnect; /* reconnect workqueue job */ struct mutex reconnect_mutex; /* prevent simultaneous reconnects */ unsigned long echo_interval; + + /* + * Number of targets available for reconnect. The more targets + * the more tasks have to wait to let the demultiplex thread + * reconnect. + */ + int nr_targets; }; static inline unsigned int diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 658a0d191056..ef51cf748db7 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -57,6 +57,7 @@ #include "smb2proto.h" #include "smbdirect.h" #include "dns_resolve.h" +#include "cifsfs.h" #ifdef CONFIG_CIFS_DFS_UPCALL #include "dfs_cache.h" #endif @@ -322,6 +323,116 @@ static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink); static void cifs_prune_tlinks(struct work_struct *work); static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data, const char *devname, bool is_smb3); +static char *extract_hostname(const char *unc); + +#ifdef CONFIG_CIFS_DFS_UPCALL +struct super_cb_data { + struct TCP_Server_Info *server; + struct cifs_sb_info *cifs_sb; +}; + +/* These functions must be called with server->srv_mutex held */ + +static void super_cb(struct super_block *sb, void *arg) +{ + struct super_cb_data *d = arg; + struct cifs_sb_info *cifs_sb; + struct cifs_tcon *tcon; + + if (d->cifs_sb) + return; + + cifs_sb = CIFS_SB(sb); + tcon = cifs_sb_master_tcon(cifs_sb); + if (tcon->ses->server == d->server) + d->cifs_sb = cifs_sb; +} + +static inline struct cifs_sb_info * +find_super_by_tcp(struct TCP_Server_Info *server) +{ + struct super_cb_data d = { + .server = server, + .cifs_sb = NULL, + }; + + iterate_supers_type(&cifs_fs_type, super_cb, &d); + return d.cifs_sb ? d.cifs_sb : ERR_PTR(-ENOENT); +} + +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) +{ + const char *name; + int rc; + char *ipaddr = NULL; + char *unc; + int len; + + if (!cifs_sb || !cifs_sb->origin_fullpath || !tgt_list || + !server->nr_targets) + return; + + if (!*tgt_it) { + *tgt_it = dfs_cache_get_tgt_iterator(tgt_list); + } else { + *tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it); + if (!*tgt_it) + *tgt_it = dfs_cache_get_tgt_iterator(tgt_list); + } + + cifs_dbg(FYI, "%s: UNC: %s\n", __func__, cifs_sb->origin_fullpath); + + name = dfs_cache_get_tgt_name(*tgt_it); + + kfree(server->hostname); + + server->hostname = extract_hostname(name); + if (!server->hostname) { + cifs_dbg(FYI, "%s: failed to extract hostname from target: %d\n", + __func__, -ENOMEM); + return; + } + + len = strlen(server->hostname) + 3; + + unc = kmalloc(len, GFP_KERNEL); + if (!unc) { + cifs_dbg(FYI, "%s: failed to create UNC path\n", __func__); + return; + } + snprintf(unc, len, "\\\\%s", server->hostname); + + rc = dns_resolve_server_name_to_ip(unc, &ipaddr); + kfree(unc); + + if (rc < 0) { + cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n", + __func__, server->hostname, rc); + return; + } + + rc = cifs_convert_address((struct sockaddr *)&server->dstaddr, ipaddr, + strlen(ipaddr)); + kfree(ipaddr); + + if (!rc) { + cifs_dbg(FYI, "%s: failed to get ipaddr out of hostname\n", + __func__); + } +} + +static inline int reconn_setup_dfs_targets(struct cifs_sb_info *cifs_sb, + struct dfs_cache_tgt_list *tl, + struct dfs_cache_tgt_iterator **it) +{ + if (!cifs_sb->origin_fullpath) + return -EOPNOTSUPP; + return dfs_cache_noreq_find(cifs_sb->origin_fullpath + 1, NULL, tl); +} +#endif /* * cifs tcp session reconnection @@ -340,8 +451,33 @@ cifs_reconnect(struct TCP_Server_Info *server) struct cifs_tcon *tcon; struct mid_q_entry *mid_entry; struct list_head retry_list; +#ifdef CONFIG_CIFS_DFS_UPCALL + struct cifs_sb_info *cifs_sb; + struct dfs_cache_tgt_list tgt_list; + struct dfs_cache_tgt_iterator *tgt_it = NULL; +#endif spin_lock(&GlobalMid_Lock); + server->nr_targets = 1; +#ifdef CONFIG_CIFS_DFS_UPCALL + cifs_sb = find_super_by_tcp(server); + if (IS_ERR(cifs_sb)) { + rc = PTR_ERR(cifs_sb); + cifs_dbg(FYI, "%s: will not do DFS failover: rc = %d\n", + __func__, rc); + cifs_sb = NULL; + } else { + rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list, &tgt_it); + if (rc) { + cifs_dbg(VFS, "%s: no target servers for DFS failover\n", + __func__); + } else { + server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list); + } + } + cifs_dbg(FYI, "%s: will retry %d target(s)\n", __func__, + server->nr_targets); +#endif if (server->tcpStatus == CifsExiting) { /* the demux thread will exit normally next time through the loop */ @@ -415,14 +551,22 @@ cifs_reconnect(struct TCP_Server_Info *server) do { try_to_freeze(); - /* we should try only the port we connected to before */ mutex_lock(&server->srv_mutex); + /* + * Set up next DFS target server (if any) for reconnect. If DFS + * feature is disabled, then we will retry last server we + * connected to before. + */ if (cifs_rdma_enabled(server)) rc = smbd_reconnect(server); else rc = generic_ip_connect(server); if (rc) { cifs_dbg(FYI, "reconnect error %d\n", rc); +#ifdef CONFIG_CIFS_DFS_UPCALL + reconn_inval_dfs_target(server, cifs_sb, &tgt_list, + &tgt_it); +#endif mutex_unlock(&server->srv_mutex); msleep(3000); } else { @@ -435,6 +579,22 @@ cifs_reconnect(struct TCP_Server_Info *server) } } while (server->tcpStatus == CifsNeedReconnect); +#ifdef CONFIG_CIFS_DFS_UPCALL + if (tgt_it) { + rc = dfs_cache_noreq_update_tgthint(cifs_sb->origin_fullpath + 1, + tgt_it); + if (rc) { + cifs_dbg(VFS, "%s: failed to update DFS target hint: rc = %d\n", + __func__, rc); + } + rc = dfs_cache_update_vol(cifs_sb->origin_fullpath, server); + if (rc) { + cifs_dbg(VFS, "%s: failed to update vol info in DFS cache: rc = %d\n", + __func__, rc); + } + } + dfs_cache_free_tgts(&tgt_list); +#endif if (server->tcpStatus == CifsNeedNegotiate) mod_delayed_work(cifsiod_wq, &server->echo, 0); @@ -2471,6 +2631,8 @@ smbd_connected: } tcp_ses->tcpStatus = CifsNeedNegotiate; + tcp_ses->nr_targets = 1; + /* thread spawned, put it on the list */ spin_lock(&cifs_tcp_ses_lock); list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list); @@ -4303,6 +4465,12 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) 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 @@ -4409,6 +4577,22 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) 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_KERNEL); + if (!cifs_sb->origin_fullpath) { + spin_unlock(&cifs_tcp_ses_lock); + rc = -ENOMEM; + goto error; + } + spin_unlock(&cifs_tcp_ses_lock); + /* * After reconnecting to a different server, unique ids won't * match anymore, so we disable serverino. This prevents @@ -4650,6 +4834,9 @@ cifs_umount(struct cifs_sb_info *cifs_sb) kfree(cifs_sb->mountdata); kfree(cifs_sb->prepath); +#ifdef CONFIG_CIFS_DFS_UPCALL + kfree(cifs_sb->origin_fullpath); +#endif call_rcu(&cifs_sb->rcu, delayed_free); } From 2f0a617448945a2c63d68983212302e9f2f8ae2f Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 18 Dec 2018 01:34:39 +0000 Subject: [PATCH 31/39] cifs: Use GFP_ATOMIC when a lock is held in cifs_mount() A spin lock is held before kstrndup, it may sleep with holding the spinlock, so we should use GFP_ATOMIC instead. Fixes: e58c31d5e387 ("cifs: Add support for failover in cifs_reconnect()") Signed-off-by: YueHaibing Signed-off-by: Steve French Reviewed-by: Paulo Alcantara --- fs/cifs/connect.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index ef51cf748db7..8de2173efdc5 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -4585,7 +4585,8 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) tcon->remap = cifs_remap(cifs_sb); } cifs_sb->origin_fullpath = kstrndup(tcon->dfs_path, - strlen(tcon->dfs_path), GFP_KERNEL); + strlen(tcon->dfs_path), + GFP_ATOMIC); if (!cifs_sb->origin_fullpath) { spin_unlock(&cifs_tcp_ses_lock); rc = -ENOMEM; From e511d31753e3f7c13bbd402399ffac676d451a7c Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 17:16:44 -0200 Subject: [PATCH 32/39] cifs: start DFS cache refresher in cifs_mount() Start the DFS cache refresh worker per volume during cifs mount. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 8de2173efdc5..ec2498f6773e 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -4594,6 +4594,11 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) } spin_unlock(&cifs_tcp_ses_lock); + rc = dfs_cache_add_vol(vol, cifs_sb->origin_fullpath); + if (rc) { + kfree(cifs_sb->origin_fullpath); + goto error; + } /* * After reconnecting to a different server, unique ids won't * match anymore, so we disable serverino. This prevents @@ -4836,6 +4841,7 @@ cifs_umount(struct cifs_sb_info *cifs_sb) kfree(cifs_sb->mountdata); kfree(cifs_sb->prepath); #ifdef CONFIG_CIFS_DFS_UPCALL + dfs_cache_del_vol(cifs_sb->origin_fullpath); kfree(cifs_sb->origin_fullpath); #endif call_rcu(&cifs_sb->rcu, delayed_free); From 23324407143dd923660378fd9b40b9a711972269 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 20 Nov 2018 14:37:18 -0200 Subject: [PATCH 33/39] cifs: Only free DFS target list if we actually got one Fix potential NULL ptr deref when DFS target list is empty. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index ec2498f6773e..80ef165d5f33 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -452,8 +452,8 @@ cifs_reconnect(struct TCP_Server_Info *server) struct mid_q_entry *mid_entry; struct list_head retry_list; #ifdef CONFIG_CIFS_DFS_UPCALL - struct cifs_sb_info *cifs_sb; - struct dfs_cache_tgt_list tgt_list; + struct cifs_sb_info *cifs_sb = NULL; + struct dfs_cache_tgt_list tgt_list = {0}; struct dfs_cache_tgt_iterator *tgt_it = NULL; #endif @@ -592,8 +592,8 @@ cifs_reconnect(struct TCP_Server_Info *server) cifs_dbg(VFS, "%s: failed to update vol info in DFS cache: rc = %d\n", __func__, rc); } + dfs_cache_free_tgts(&tgt_list); } - dfs_cache_free_tgts(&tgt_list); #endif if (server->tcpStatus == CifsNeedNegotiate) mod_delayed_work(cifsiod_wq, &server->echo, 0); From a3a53b7603798fd875e2afbba7e2b9ba6b19c7c7 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 17:20:31 -0200 Subject: [PATCH 34/39] cifs: Add support for failover in smb2_reconnect() After a successful failover in cifs_reconnect(), the smb2_reconnect() function will make sure to reconnect every tcon to new target server. For SMB2+. Signed-off-by: Paulo Alcantara Signed-off-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifsproto.h | 2 ++ fs/cifs/misc.c | 17 +++++++++ fs/cifs/smb2pdu.c | 88 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index f277bc5a0c4e..336c116995d7 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -565,6 +565,8 @@ void cifs_free_hash(struct crypto_shash **shash, struct sdesc **sdesc); extern void rqst_page_get_length(struct smb_rqst *rqst, unsigned int page, unsigned int *len, unsigned int *offset); +void extract_unc_hostname(const char *unc, const char **h, size_t *len); + #ifdef CONFIG_CIFS_DFS_UPCALL static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses, const char *old_path, diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index 7c858d4c66f3..bee203055b30 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -952,3 +952,20 @@ void rqst_page_get_length(struct smb_rqst *rqst, unsigned int page, else if (page == 0) *len = rqst->rq_pagesz - rqst->rq_offset; } + +void extract_unc_hostname(const char *unc, const char **h, size_t *len) +{ + const char *end; + + /* skip initial slashes */ + while (*unc && (*unc == '\\' || *unc == '/')) + unc++; + + end = unc; + + while (*end && !(*end == '\\' || *end == '/')) + end++; + + *h = unc; + *len = end - unc; +} diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 67ce3399400b..e283590955cd 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -50,6 +50,9 @@ #include "cifs_spnego.h" #include "smbdirect.h" #include "trace.h" +#ifdef CONFIG_CIFS_DFS_UPCALL +#include "dfs_cache.h" +#endif /* * The following table defines the expected "StructureSize" of SMB2 requests @@ -152,6 +155,77 @@ out: return; } +#ifdef CONFIG_CIFS_DFS_UPCALL +static int __smb2_reconnect(const struct nls_table *nlsc, + struct cifs_tcon *tcon) +{ + int rc; + struct dfs_cache_tgt_list tl; + struct dfs_cache_tgt_iterator *it = NULL; + char tree[MAX_TREE_SIZE + 1]; + const char *tcp_host; + size_t tcp_host_len; + const char *dfs_host; + size_t dfs_host_len; + + if (tcon->ipc) { + snprintf(tree, sizeof(tree), "\\\\%s\\IPC$", + tcon->ses->server->hostname); + return SMB2_tcon(0, tcon->ses, tree, tcon, nlsc); + } + + if (!tcon->dfs_path) + return SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nlsc); + + rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl); + if (rc) + return rc; + + extract_unc_hostname(tcon->ses->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 *tgt = dfs_cache_get_tgt_name(it); + + extract_unc_hostname(tgt, &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: skipping %.*s, doesn't match %.*s", + __func__, + (int)dfs_host_len, dfs_host, + (int)tcp_host_len, tcp_host); + continue; + } + + snprintf(tree, sizeof(tree), "\\%s", tgt); + + rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc); + if (!rc) + 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); + 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) { @@ -159,6 +233,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon) struct nls_table *nls_codepage; struct cifs_ses *ses; struct TCP_Server_Info *server; + int retries; /* * SMB2s NegProt, SessSetup, Logoff do not have tcon yet so @@ -192,9 +267,12 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon) ses = tcon->ses; server = ses->server; + retries = server->nr_targets; + /* - * Give demultiplex thread up to 10 seconds to reconnect, should be - * greater than cifs socket timeout which is 7 seconds + * Give demultiplex thread up to 10 seconds to each target available for + * reconnect -- should be greater than cifs socket timeout which is 7 + * seconds. */ while (server->tcpStatus == CifsNeedReconnect) { /* @@ -225,6 +303,9 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon) if (server->tcpStatus != CifsNeedReconnect) break; + if (--retries) + continue; + /* * on "soft" mounts we wait once. Hard mounts keep * retrying until process is killed or server comes @@ -234,6 +315,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon) cifs_dbg(FYI, "gave up waiting on reconnect in smb_init\n"); return -EHOSTDOWN; } + retries = server->nr_targets; } if (!tcon->ses->need_reconnect && !tcon->need_reconnect) @@ -271,7 +353,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon) if (tcon->use_persistent) tcon->need_reopen_files = true; - rc = SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nls_codepage); + rc = __smb2_reconnect(nls_codepage, tcon); mutex_unlock(&tcon->ses->session_mutex); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); From 08744015492fec5a30ab8e2779601ae2b1a5e431 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 14 Nov 2018 17:24:29 -0200 Subject: [PATCH 35/39] cifs: Add support for failover in cifs_reconnect_tcon() After a successful failover, the cifs_reconnect_tcon() function will make sure to reconnect every tcon to new target server. Same as previous commit but for SMB1 codepath. Signed-off-by: Paulo Alcantara Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifssmb.c | 88 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c index f82fd342bca5..b1f49c1c543a 100644 --- a/fs/cifs/cifssmb.c +++ b/fs/cifs/cifssmb.c @@ -44,6 +44,9 @@ #include "cifs_debug.h" #include "fscache.h" #include "smbdirect.h" +#ifdef CONFIG_CIFS_DFS_UPCALL +#include "dfs_cache.h" +#endif #ifdef CONFIG_CIFS_POSIX static struct { @@ -118,6 +121,77 @@ 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 dfs_cache_tgt_list tl; + struct dfs_cache_tgt_iterator *it = NULL; + char tree[MAX_TREE_SIZE + 1]; + const char *tcp_host; + size_t tcp_host_len; + const char *dfs_host; + size_t dfs_host_len; + + if (tcon->ipc) { + snprintf(tree, sizeof(tree), "\\\\%s\\IPC$", + tcon->ses->server->hostname); + return CIFSTCon(0, tcon->ses, tree, tcon, nlsc); + } + + if (!tcon->dfs_path) + return CIFSTCon(0, tcon->ses, tcon->treeName, tcon, nlsc); + + rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl); + if (rc) + return rc; + + extract_unc_hostname(tcon->ses->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 *tgt = dfs_cache_get_tgt_name(it); + + extract_unc_hostname(tgt, &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: skipping %.*s, doesn't match %.*s", + __func__, + (int)dfs_host_len, dfs_host, + (int)tcp_host_len, tcp_host); + continue; + } + + snprintf(tree, sizeof(tree), "\\%s", tgt); + + rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc); + if (!rc) + 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); + 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) @@ -126,6 +200,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) struct cifs_ses *ses; struct TCP_Server_Info *server; struct nls_table *nls_codepage; + int retries; /* * SMBs NegProt, SessSetup, uLogoff do not have tcon yet so check for @@ -152,9 +227,12 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) } } + retries = server->nr_targets; + /* - * Give demultiplex thread up to 10 seconds to reconnect, should be - * greater than cifs socket timeout which is 7 seconds + * Give demultiplex thread up to 10 seconds to each target available for + * reconnect -- should be greater than cifs socket timeout which is 7 + * seconds. */ while (server->tcpStatus == CifsNeedReconnect) { rc = wait_event_interruptible_timeout(server->response_q, @@ -170,6 +248,9 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) if (server->tcpStatus != CifsNeedReconnect) break; + if (--retries) + continue; + /* * on "soft" mounts we wait once. Hard mounts keep * retrying until process is killed or server comes @@ -179,6 +260,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) cifs_dbg(FYI, "gave up waiting on reconnect in smb_init\n"); return -EHOSTDOWN; } + retries = server->nr_targets; } if (!ses->need_reconnect && !tcon->need_reconnect) @@ -214,7 +296,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) } cifs_mark_open_files_invalid(tcon); - rc = CIFSTCon(0, ses, tcon->treeName, tcon, nls_codepage); + rc = __cifs_reconnect_tcon(nls_codepage, tcon); mutex_unlock(&ses->session_mutex); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); From 28eb24ff75c5ac130eb326b3b4d0dcecfc0f427d Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 20 Nov 2018 15:16:36 -0200 Subject: [PATCH 36/39] cifs: Always resolve hostname before reconnecting In case a hostname resolves to a different IP address (e.g. long running mounts), make sure to resolve it every time prior to calling generic_ip_connect() in reconnect. Suggested-by: Steve French Signed-off-by: Paulo Alcantara Signed-off-by: Steve French --- fs/cifs/connect.c | 84 +++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 80ef165d5f33..69b9d5606eba 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -325,6 +325,53 @@ static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data, const char *devname, bool is_smb3); static char *extract_hostname(const char *unc); +/* + * Resolve hostname and set ip addr in tcp ses. Useful for hostnames that may + * get their ip addresses changed at some point. + * + * This should be called with server->srv_mutex held. + */ +#ifdef CONFIG_CIFS_DFS_UPCALL +static int reconn_set_ipaddr(struct TCP_Server_Info *server) +{ + int rc; + int len; + char *unc, *ipaddr = NULL; + + if (!server->hostname) + return -EINVAL; + + len = strlen(server->hostname) + 3; + + unc = kmalloc(len, GFP_KERNEL); + if (!unc) { + cifs_dbg(FYI, "%s: failed to create UNC path\n", __func__); + return -ENOMEM; + } + snprintf(unc, len, "\\\\%s", server->hostname); + + rc = dns_resolve_server_name_to_ip(unc, &ipaddr); + kfree(unc); + + if (rc < 0) { + cifs_dbg(FYI, "%s: failed to resolve server part of %s to IP: %d\n", + __func__, server->hostname, rc); + return rc; + } + + rc = cifs_convert_address((struct sockaddr *)&server->dstaddr, ipaddr, + strlen(ipaddr)); + kfree(ipaddr); + + return !rc ? -1 : 0; +} +#else +static inline int reconn_set_ipaddr(struct TCP_Server_Info *server) +{ + return 0; +} +#endif + #ifdef CONFIG_CIFS_DFS_UPCALL struct super_cb_data { struct TCP_Server_Info *server; @@ -366,10 +413,6 @@ static void reconn_inval_dfs_target(struct TCP_Server_Info *server, struct dfs_cache_tgt_iterator **tgt_it) { const char *name; - int rc; - char *ipaddr = NULL; - char *unc; - int len; if (!cifs_sb || !cifs_sb->origin_fullpath || !tgt_list || !server->nr_targets) @@ -393,34 +436,6 @@ static void reconn_inval_dfs_target(struct TCP_Server_Info *server, if (!server->hostname) { cifs_dbg(FYI, "%s: failed to extract hostname from target: %d\n", __func__, -ENOMEM); - return; - } - - len = strlen(server->hostname) + 3; - - unc = kmalloc(len, GFP_KERNEL); - if (!unc) { - cifs_dbg(FYI, "%s: failed to create UNC path\n", __func__); - return; - } - snprintf(unc, len, "\\\\%s", server->hostname); - - rc = dns_resolve_server_name_to_ip(unc, &ipaddr); - kfree(unc); - - if (rc < 0) { - cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n", - __func__, server->hostname, rc); - return; - } - - rc = cifs_convert_address((struct sockaddr *)&server->dstaddr, ipaddr, - strlen(ipaddr)); - kfree(ipaddr); - - if (!rc) { - cifs_dbg(FYI, "%s: failed to get ipaddr out of hostname\n", - __func__); } } @@ -567,6 +582,11 @@ cifs_reconnect(struct TCP_Server_Info *server) reconn_inval_dfs_target(server, cifs_sb, &tgt_list, &tgt_it); #endif + rc = reconn_set_ipaddr(server); + if (rc) { + cifs_dbg(FYI, "%s: failed to resolve hostname: %d\n", + __func__, rc); + } mutex_unlock(&server->srv_mutex); msleep(3000); } else { From 14e92c5dc7a1a1d4a82fb7142b5642837fef962a Mon Sep 17 00:00:00 2001 From: Steve French Date: Mon, 24 Dec 2018 01:05:22 -0600 Subject: [PATCH 37/39] cifs: Minor Kconfig clarification Clarify the use of the CONFIG_DFS_UPCALL for DNS name resolution when server ip addresses change (e.g. on long running mounts) Signed-off-by: Steve French --- fs/cifs/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/cifs/Kconfig b/fs/cifs/Kconfig index 85dadb93c992..f1ddc9d03c10 100644 --- a/fs/cifs/Kconfig +++ b/fs/cifs/Kconfig @@ -190,8 +190,9 @@ config CIFS_DFS_UPCALL moves to a different server. This feature also enables an upcall mechanism for CIFS which contacts userspace helper utilities to provide server name resolution (host names to - IP addresses) which is needed for implicit mounts of DFS junction - points. If unsure, say Y. + IP addresses) which is needed in order to reconnect to + servers if their addresses change or for implicit mounts of + DFS junction points. If unsure, say Y. config CIFS_NFSD_EXPORT bool "Allow nfsd to export CIFS file system" From e77fe73c7e38c36145825d84cfe385d400aba4fd Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Mon, 31 Dec 2018 13:43:40 +1000 Subject: [PATCH 38/39] cifs: we can not use small padding iovs together with encryption We can not append small padding buffers as separate iovs when encryption is used. For this case we must flatten the request into a single buffer containing both the data from all the iovs as well as the padding bytes. This is at least needed for 4.20 as well due to compounding changes. CC: Stable Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/smb2inode.c | 16 +++++------ fs/cifs/smb2ops.c | 67 +++++++++++++++++++++++++++++++-------------- fs/cifs/smb2proto.h | 5 ++-- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c index a8999f930b22..f14533da3a93 100644 --- a/fs/cifs/smb2inode.c +++ b/fs/cifs/smb2inode.c @@ -49,7 +49,6 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_open_parms oparms; struct cifs_fid fid; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = ses->server; int num_rqst = 0; struct smb_rqst rqst[3]; int resp_buftype[3]; @@ -97,7 +96,7 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, if (rc) goto finished; - smb2_set_next_command(server, &rqst[num_rqst++], 0); + smb2_set_next_command(tcon, &rqst[num_rqst++]); /* Operation */ switch (command) { @@ -111,7 +110,7 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, SMB2_O_INFO_FILE, 0, sizeof(struct smb2_file_all_info) + PATH_MAX * 2, 0, NULL); - smb2_set_next_command(server, &rqst[num_rqst], 0); + smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst++]); break; case SMB2_OP_DELETE: @@ -134,7 +133,7 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, COMPOUND_FID, current->tgid, FILE_DISPOSITION_INFORMATION, SMB2_O_INFO_FILE, 0, data, size); - smb2_set_next_command(server, &rqst[num_rqst], 1); + smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst++]); break; case SMB2_OP_SET_EOF: @@ -149,7 +148,7 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, COMPOUND_FID, current->tgid, FILE_END_OF_FILE_INFORMATION, SMB2_O_INFO_FILE, 0, data, size); - smb2_set_next_command(server, &rqst[num_rqst], 0); + smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst++]); break; case SMB2_OP_SET_INFO: @@ -165,7 +164,7 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, COMPOUND_FID, current->tgid, FILE_BASIC_INFORMATION, SMB2_O_INFO_FILE, 0, data, size); - smb2_set_next_command(server, &rqst[num_rqst], 0); + smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst++]); break; case SMB2_OP_RENAME: @@ -189,7 +188,7 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, COMPOUND_FID, current->tgid, FILE_RENAME_INFORMATION, SMB2_O_INFO_FILE, 0, data, size); - smb2_set_next_command(server, &rqst[num_rqst], 0); + smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst++]); break; case SMB2_OP_HARDLINK: @@ -213,7 +212,7 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, COMPOUND_FID, current->tgid, FILE_LINK_INFORMATION, SMB2_O_INFO_FILE, 0, data, size); - smb2_set_next_command(server, &rqst[num_rqst], 0); + smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst++]); break; default: @@ -388,7 +387,6 @@ smb2_set_path_attr(const unsigned int xid, struct cifs_tcon *tcon, rc = -ENOMEM; goto smb2_rename_path; } - rc = smb2_compound_op(xid, tcon, cifs_sb, from_name, access, FILE_OPEN, 0, smb2_to_name, command); smb2_rename_path: diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 6c99a146fcec..33100ef74d7f 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -884,7 +884,6 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb) { struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = ses->server; __le16 *utf16_path = NULL; int ea_name_len = strlen(ea_name); int flags = 0; @@ -936,7 +935,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, utf16_path); if (rc) goto sea_exit; - smb2_set_next_command(ses->server, &rqst[0], 0); + smb2_set_next_command(tcon, &rqst[0]); /* Set Info */ @@ -963,7 +962,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, COMPOUND_FID, current->tgid, FILE_FULL_EA_INFORMATION, SMB2_O_INFO_FILE, 0, data, size); - smb2_set_next_command(server, &rqst[1], 0); + smb2_set_next_command(tcon, &rqst[1]); smb2_set_related(&rqst[1]); @@ -1222,7 +1221,7 @@ smb2_ioctl_query_info(const unsigned int xid, rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, path); if (rc) goto iqinf_exit; - smb2_set_next_command(ses->server, &rqst[0], 0); + smb2_set_next_command(tcon, &rqst[0]); /* Query */ memset(&qi_iov, 0, sizeof(qi_iov)); @@ -1236,7 +1235,7 @@ smb2_ioctl_query_info(const unsigned int xid, qi.output_buffer_length, buffer); if (rc) goto iqinf_exit; - smb2_set_next_command(ses->server, &rqst[1], 0); + smb2_set_next_command(tcon, &rqst[1]); smb2_set_related(&rqst[1]); /* Close */ @@ -1789,26 +1788,53 @@ smb2_set_related(struct smb_rqst *rqst) char smb2_padding[7] = {0, 0, 0, 0, 0, 0, 0}; void -smb2_set_next_command(struct TCP_Server_Info *server, struct smb_rqst *rqst, - bool has_space_for_padding) +smb2_set_next_command(struct cifs_tcon *tcon, struct smb_rqst *rqst) { struct smb2_sync_hdr *shdr; + struct cifs_ses *ses = tcon->ses; + struct TCP_Server_Info *server = ses->server; unsigned long len = smb_rqst_len(server, rqst); + int i, num_padding; /* SMB headers in a compound are 8 byte aligned. */ - if (len & 7) { - if (has_space_for_padding) { - len = rqst->rq_iov[rqst->rq_nvec - 1].iov_len; - rqst->rq_iov[rqst->rq_nvec - 1].iov_len = - (len + 7) & ~7; - } else { - rqst->rq_iov[rqst->rq_nvec].iov_base = smb2_padding; - rqst->rq_iov[rqst->rq_nvec].iov_len = 8 - (len & 7); - rqst->rq_nvec++; + + /* No padding needed */ + if (!(len & 7)) + goto finished; + + num_padding = 8 - (len & 7); + if (!smb3_encryption_required(tcon)) { + /* + * If we do not have encryption then we can just add an extra + * iov for the padding. + */ + rqst->rq_iov[rqst->rq_nvec].iov_base = smb2_padding; + rqst->rq_iov[rqst->rq_nvec].iov_len = num_padding; + rqst->rq_nvec++; + len += num_padding; + } else { + /* + * We can not add a small padding iov for the encryption case + * because the encryption framework can not handle the padding + * iovs. + * We have to flatten this into a single buffer and add + * the padding to it. + */ + for (i = 1; i < rqst->rq_nvec; i++) { + memcpy(rqst->rq_iov[0].iov_base + + rqst->rq_iov[0].iov_len, + rqst->rq_iov[i].iov_base, + rqst->rq_iov[i].iov_len); + rqst->rq_iov[0].iov_len += rqst->rq_iov[i].iov_len; } - len = smb_rqst_len(server, rqst); + memset(rqst->rq_iov[0].iov_base + rqst->rq_iov[0].iov_len, + 0, num_padding); + rqst->rq_iov[0].iov_len += num_padding; + len += num_padding; + rqst->rq_nvec = 1; } + finished: shdr = (struct smb2_sync_hdr *)(rqst->rq_iov[0].iov_base); shdr->NextCommand = cpu_to_le32(len); } @@ -1825,7 +1851,6 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb) { struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = ses->server; int flags = 0; struct smb_rqst rqst[3]; int resp_buftype[3]; @@ -1862,7 +1887,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, utf16_path); if (rc) goto qic_exit; - smb2_set_next_command(server, &rqst[0], 0); + smb2_set_next_command(tcon, &rqst[0]); memset(&qi_iov, 0, sizeof(qi_iov)); rqst[1].rq_iov = qi_iov; @@ -1874,7 +1899,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, NULL); if (rc) goto qic_exit; - smb2_set_next_command(server, &rqst[1], 0); + smb2_set_next_command(tcon, &rqst[1]); smb2_set_related(&rqst[1]); memset(&close_iov, 0, sizeof(close_iov)); @@ -2806,7 +2831,7 @@ init_sg(int num_rqst, struct smb_rqst *rqst, u8 *sign) smb2_sg_set_buf(&sg[idx++], rqst[i].rq_iov[j].iov_base + skip, rqst[i].rq_iov[j].iov_len - skip); - } + } for (j = 0; j < rqst[i].rq_npages; j++) { unsigned int len, offset; diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h index 4029ee037ab4..87733b27a65f 100644 --- a/fs/cifs/smb2proto.h +++ b/fs/cifs/smb2proto.h @@ -116,9 +116,8 @@ extern void smb2_reconnect_server(struct work_struct *work); extern int smb3_crypto_aead_allocate(struct TCP_Server_Info *server); extern unsigned long smb_rqst_len(struct TCP_Server_Info *server, struct smb_rqst *rqst); -extern void smb2_set_next_command(struct TCP_Server_Info *server, - struct smb_rqst *rqst, - bool has_space_for_padding); +extern void smb2_set_next_command(struct cifs_tcon *tcon, + struct smb_rqst *rqst); extern void smb2_set_related(struct smb_rqst *rqst); /* From fea170804b4dc44cd79f8cb1ce236f3a824951cd Mon Sep 17 00:00:00 2001 From: Steve French Date: Tue, 6 Nov 2018 16:20:46 -0600 Subject: [PATCH 39/39] cifs: update internal module version number To version 2.15 Signed-off-by: Steve French --- fs/cifs/cifsfs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index 4c3b5cfccc49..26776eddd85d 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h @@ -150,5 +150,5 @@ extern long cifs_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); extern const struct export_operations cifs_export_ops; #endif /* CONFIG_CIFS_NFSD_EXPORT */ -#define CIFS_VERSION "2.14" +#define CIFS_VERSION "2.15" #endif /* _CIFSFS_H */