From patchwork Thu Nov 15 14:20:50 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998358 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk7Y4hn5z9s3q for ; Fri, 16 Nov 2018 01:21:29 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1732953AbeKPA3a (ORCPT ); Thu, 15 Nov 2018 19:29:30 -0500 Received: from mx2.suse.de ([195.135.220.15]:42540 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1729035AbeKPA3a (ORCPT ); Thu, 15 Nov 2018 19:29:30 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay1.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 9CFE5AD70; Thu, 15 Nov 2018 14:21:25 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 01/14] cifs: Refactor out cifs_mount() Date: Thu, 15 Nov 2018 15:20:50 +0100 Message-Id: <20181115142103.24617-2-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara * 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 Signed-off-by: Aurelien Aptel --- fs/cifs/cifsproto.h | 4 +- fs/cifs/connect.c | 441 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 262 insertions(+), 183 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 6f24f129a751..3caa2021a5d6 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3746,8 +3746,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); @@ -3762,10 +3762,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 /* @@ -3845,7 +3971,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); } @@ -3954,107 +4080,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; - unsigned int xid; - struct cifs_ses *ses; - struct cifs_tcon *tcon; - struct TCP_Server_Info *server; - char *full_path; - struct tcon_link *tlink; -#ifdef CONFIG_CIFS_DFS_UPCALL - int referral_walks_count = 0; -#endif - -#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; + char *full_path; - free_xid(xid); - } -#endif - rc = 0; - tcon = NULL; - ses = NULL; - server = NULL; - full_path = NULL; - tlink = NULL; + if (!server->ops->is_path_accessible) + return -ENOSYS; - xid = get_xid(); + /* + * 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; - /* 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; - } + cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); - 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; + rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, + full_path); + if (rc != 0 && rc != -EREMOTE) { + kfree(full_path); + return rc; } - /* 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 (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; + } } - /* 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; + kfree(full_path); + return rc; +} - /* 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 */ +#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 = NULL; + struct TCP_Server_Info *server; + char *old_mountdata; + int count; - /* do not care if a following call succeed - informational */ - if (!tcon->pipe && server->ops->qfs_tcon) - server->ops->qfs_tcon(xid, tcon); + 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 (rc == -EACCES || rc == -EOPNOTSUPP) + goto error; - 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 @@ -4062,119 +4158,100 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info) * 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; - } + old_mountdata = cifs_sb->mountdata; + (void)expand_dfs_referral(xid, ses, vol, cifs_sb, false); + + if (cifs_sb->mountdata == NULL) { + rc = -ENOENT; + goto error; } -#endif - /* 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 != 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; - } + goto error; - 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); +out: + free_xid(xid); + return mount_setup_tlink(cifs_sb, ses, tcon); - 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); +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; - queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks, - TLINK_IDLE_EXPIRE); + rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); + if (rc) + goto error; -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); + if (tcon) { + rc = is_path_remote(cifs_sb, vol, xid, server, tcon); + if (rc == -EREMOTE) + rc = -EOPNOTSUPP; + if (rc) + 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; } +#endif /* * Issue a TREE_CONNECT request. From patchwork Thu Nov 15 14:20:51 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998359 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk7b225Yz9s9m for ; Fri, 16 Nov 2018 01:21:31 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387833AbeKPA3c (ORCPT ); Thu, 15 Nov 2018 19:29:32 -0500 Received: from mx2.suse.de ([195.135.220.15]:42554 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1729035AbeKPA3c (ORCPT ); Thu, 15 Nov 2018 19:29:32 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 406D5AF3E; Thu, 15 Nov 2018 14:21:29 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 02/14] cifs: Skip any trailing backslashes from UNC Date: Thu, 15 Nov 2018 15:20:51 +0100 Message-Id: <20181115142103.24617-3-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara When extracting hostname from UNC, check for leading backslashes before trying to remove them. Signed-off-by: Paulo Alcantara Signed-off-by: Aurelien Aptel --- 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 3caa2021a5d6..7022678be860 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -1043,7 +1043,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 patchwork Thu Nov 15 14:20:52 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998360 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk7f2vTDz9s3q for ; Fri, 16 Nov 2018 01:21:34 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729035AbeKPA3f (ORCPT ); Thu, 15 Nov 2018 19:29:35 -0500 Received: from mx2.suse.de ([195.135.220.15]:42572 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S2387839AbeKPA3f (ORCPT ); Thu, 15 Nov 2018 19:29:35 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay1.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 6F1A9AD70; Thu, 15 Nov 2018 14:21:32 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 03/14] cifs: Fix separator when building path from dentry Date: Thu, 15 Nov 2018 15:20:52 +0100 Message-Id: <20181115142103.24617-4-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara Make sure to use the CIFS_DIR_SEP(cifs_sb) as path separator for prefixpath too. Fixes a bug with smb1 UNIX extensions. Fixes: a6b5058fafdf ("fs/cifs: make share unaccessible at root level mountable") Signed-off-by: Aurelien Aptel --- fs/cifs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/cifs/dir.c b/fs/cifs/dir.c index 3713d22b95a7..907e85d65bb4 100644 --- a/fs/cifs/dir.c +++ b/fs/cifs/dir.c @@ -174,7 +174,7 @@ build_path_from_dentry_optional_prefix(struct dentry *direntry, bool prefix) cifs_dbg(FYI, "using cifs_sb prepath <%s>\n", cifs_sb->prepath); memcpy(full_path+dfsplen+1, cifs_sb->prepath, pplen-1); - full_path[dfsplen] = '\\'; + full_path[dfsplen] = dirsep; for (i = 0; i < pplen-1; i++) if (full_path[dfsplen+1+i] == '/') full_path[dfsplen+1+i] = CIFS_DIR_SEP(cifs_sb); From patchwork Thu Nov 15 14:20:53 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998361 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk7k1qxrz9s3q for ; Fri, 16 Nov 2018 01:21:38 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387922AbeKPA3j (ORCPT ); Thu, 15 Nov 2018 19:29:39 -0500 Received: from mx2.suse.de ([195.135.220.15]:42596 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S2387839AbeKPA3j (ORCPT ); Thu, 15 Nov 2018 19:29:39 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay1.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 36EE9AD70; Thu, 15 Nov 2018 14:21:36 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 04/14] cifs: Make devname param optional in cifs_compose_mount_options() Date: Thu, 15 Nov 2018 15:20:53 +0100 Message-Id: <20181115142103.24617-5-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara 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 Signed-off-by: Aurelien Aptel --- 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 @@ char *cifs_compose_mount_options(const char *sb_mountdata, compose_mount_options_err: kfree(mountdata); mountdata = ERR_PTR(rc); - kfree(*devname); - *devname = NULL; + kfree(name); goto compose_mount_options_out; } From patchwork Thu Nov 15 14:20:54 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998362 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk7p2JS6z9s3q for ; Fri, 16 Nov 2018 01:21:42 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388019AbeKPA3n (ORCPT ); Thu, 15 Nov 2018 19:29:43 -0500 Received: from mx2.suse.de ([195.135.220.15]:42620 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S2387839AbeKPA3n (ORCPT ); Thu, 15 Nov 2018 19:29:43 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 3AB1CAF4C; Thu, 15 Nov 2018 14:21:40 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 05/14] cifs: Respect -EAGAIN when querying paths Date: Thu, 15 Nov 2018 15:20:54 +0100 Message-Id: <20181115142103.24617-6-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara After a successful reconnect, smb2_reconnect() will return -EAGAIN for specific SMB2 commands (including queries) informing the caller to retry the command. This patch makes sure to retry them in possible reconnects. Signed-off-by: Paulo Alcantara Signed-off-by: Aurelien Aptel --- fs/cifs/inode.c | 7 +++++-- fs/cifs/smb2ops.c | 23 ++++++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index a81a9df997c1..44a76e26f21f 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -771,8 +771,11 @@ cifs_get_inode_info(struct inode **inode, const char *full_path, goto cgii_exit; } data = (FILE_ALL_INFO *)buf; - rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path, - data, &adjust_tz, &symlink); + do { + rc = server->ops->query_path_info(xid, tcon, cifs_sb, + full_path, data, + &adjust_tz, &symlink); + } while (rc == -EAGAIN); } if (!rc) { diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 54bedd751e66..dd2e8b4e4cc9 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -1714,8 +1714,13 @@ smb2_query_dir_first(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); + do { + rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL, + NULL); + } while (rc == -EAGAIN); + kfree(utf16_path); + if (rc) { cifs_dbg(FYI, "open dir failed rc=%d\n", rc); return rc; @@ -1724,8 +1729,11 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon, srch_inf->entries_in_buffer = 0; srch_inf->index_of_last_entry = 2; - rc = SMB2_query_directory(xid, tcon, fid->persistent_fid, - fid->volatile_fid, 0, srch_inf); + do { + rc = SMB2_query_directory(xid, tcon, fid->persistent_fid, + fid->volatile_fid, 0, srch_inf); + } while (rc == -EAGAIN); + if (rc) { cifs_dbg(FYI, "query directory failed rc=%d\n", rc); SMB2_close(xid, tcon, fid->persistent_fid, fid->volatile_fid); @@ -1738,8 +1746,13 @@ smb2_query_dir_next(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_fid *fid, __u16 search_flags, struct cifs_search_info *srch_inf) { - return SMB2_query_directory(xid, tcon, fid->persistent_fid, - fid->volatile_fid, 0, srch_inf); + int rc; + + do { + rc = SMB2_query_directory(xid, tcon, fid->persistent_fid, + fid->volatile_fid, 0, srch_inf); + } while (rc == -EAGAIN); + return rc; } static int From patchwork Thu Nov 15 14:20:55 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998363 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk7s3k4jz9s3q for ; Fri, 16 Nov 2018 01:21:45 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388042AbeKPA3q (ORCPT ); Thu, 15 Nov 2018 19:29:46 -0500 Received: from mx2.suse.de ([195.135.220.15]:42640 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S2387839AbeKPA3q (ORCPT ); Thu, 15 Nov 2018 19:29:46 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay1.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 8E3EBAD70; Thu, 15 Nov 2018 14:21:43 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 06/14] cifs: Save TTL value when parsing DFS referrals Date: Thu, 15 Nov 2018 15:20:55 +0100 Message-Id: <20181115142103.24617-7-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara This will be needed by DFS cache. Signed-off-by: Paulo Alcantara Signed-off-by: Aurelien Aptel --- 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 38ab0fca49e1..9534f46e6ad2 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 8a41f4eba726..7d8d9423d5ba 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -732,6 +732,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 patchwork Thu Nov 15 14:20:56 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998364 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk7y5fbzz9s3q for ; Fri, 16 Nov 2018 01:21:50 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388084AbeKPA3w (ORCPT ); Thu, 15 Nov 2018 19:29:52 -0500 Received: from mx2.suse.de ([195.135.220.15]:42654 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S2387839AbeKPA3v (ORCPT ); Thu, 15 Nov 2018 19:29:51 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 29EA2AD70; Thu, 15 Nov 2018 14:21:48 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 07/14] cifs: auto disable 'serverino' in dfs mounts Date: Thu, 15 Nov 2018 15:20:56 +0100 Message-Id: <20181115142103.24617-8-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara 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 --- 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 7022678be860..86649230b4f2 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -4219,6 +4219,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 44a76e26f21f..8bb7b1b45f74 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; @@ -824,7 +823,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); } @@ -843,31 +841,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 7d8d9423d5ba..5e315e4009e2 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 patchwork Thu Nov 15 14:20:57 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998367 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk8N4CGVz9s3q for ; Fri, 16 Nov 2018 01:22:12 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387548AbeKPAaN (ORCPT ); Thu, 15 Nov 2018 19:30:13 -0500 Received: from mx2.suse.de ([195.135.220.15]:42732 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1732953AbeKPAaN (ORCPT ); Thu, 15 Nov 2018 19:30:13 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 039A8AD70; Thu, 15 Nov 2018 14:22:06 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 08/14] cifs: Add DFS cache routines Date: Thu, 15 Nov 2018 15:20:57 +0100 Message-Id: <20181115142103.24617-9-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara * 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 --- 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 | 1379 ++++++++++++++++++++++++++++++++++++++++++++++++++ fs/cifs/dfs_cache.h | 97 ++++ 7 files changed, 1498 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 9534f46e6ad2..bd183ec17066 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 86649230b4f2..b4fed69add86 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2294,7 +2294,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..18da279e9354 --- /dev/null +++ b/fs/cifs/dfs_cache.c @@ -0,0 +1,1379 @@ +// 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 && !strncasecmp(t->t_name, tgthint, + strlen(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, int len) +{ + struct dfs_cache_entry *ce; + bool found = false; + + rcu_read_lock(); + hlist_for_each_entry_rcu(ce, &dfs_cache_htable[hash], ce_hlist) { + if (!strncasecmp(ce->ce_path, path, len)) { +#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) +{ + int len = strlen(path); + + *hash = cache_entry_hash(path, len); + return __find_cache_entry(*hash, path, len); +} + +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(-ENOSYS); + 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(!strncasecmp(it->it_name, t->t_name, strlen(t->t_name)))) + goto out; + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + if (!strncasecmp(t->t_name, it->it_name, strlen(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(!strncasecmp(it->it_name, t->t_name, strlen(t->t_name)))) + goto out; + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + if (!strncasecmp(t->t_name, it->it_name, strlen(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 (!strncasecmp(vi->vi_fullpath, fullpath, + strlen(vi->vi_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. + * + * Return zero if volume was deleted, otherwise non-zero. + */ +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; + + cifs_dbg(FYI, "%s: dfs path: %s\n", __func__, path); + + 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 = -ENOSYS; + } 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; + + cifs_dbg(FYI, "%s: refreshing DFS referral cache\n", __func__); + + mutex_lock(&dc->dc_lock); + + list_for_each_entry(vi, &dc->dc_vol_list, vi_list) { + cifs_dbg(FYI, "%s: vol path: %s\n", __func__, vi->vi_fullpath); + server = cifs_find_tcp_session(&vi->vi_vol); + if (!server) + continue; + cifs_dbg(FYI, "%s: found a tcp ses: %p\n", __func__, server); + 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); + + cifs_dbg(FYI, "%s: finished refreshing DFS referral cache\n", __func__); +} 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 patchwork Thu Nov 15 14:20:58 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998368 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk8V4SVsz9s3q for ; Fri, 16 Nov 2018 01:22:18 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387839AbeKPAaT (ORCPT ); Thu, 15 Nov 2018 19:30:19 -0500 Received: from mx2.suse.de ([195.135.220.15]:42804 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1732953AbeKPAaT (ORCPT ); Thu, 15 Nov 2018 19:30:19 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 6972FAD70; Thu, 15 Nov 2018 14:22:14 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 09/14] cifs: Make use of DFS cache to get new DFS referrals Date: Thu, 15 Nov 2018 15:20:58 +0100 Message-Id: <20181115142103.24617-10-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara 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 Signed-off-by: Aurelien Aptel --- fs/cifs/cifs_dfs_ref.c | 100 ++++++++++++++++++++++++++++++------------------- 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, 113 insertions(+), 84 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; + } + + tcon = cifs_sb_master_tcon(cifs_sb); + if (!tcon) { + cifs_dbg(FYI, "%s: master tcon is NULL\n", __func__); goto free_full_path; } - ses = tlink_tcon(tlink)->ses; + 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); + + ses = tcon->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; + /* + * 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); } - /* no valid submounts were found; return error from get_dfs_path() by - * preference */ - if (rc != 0) + free_xid(xid); + + if (rc) { mnt = ERR_PTR(rc); + goto free_root_path; + } -success: - free_dfs_info_array(referrals, num_referrals); + 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 @@ init_cifs(void) #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(); @@ -1556,6 +1568,9 @@ exit_cifs(void) #ifdef CONFIG_CIFS_UPCALL exit_cifs_spnego(); #endif +#ifdef CONFIG_CIFS_DFS_UPCALL + dfs_cache_destroy(); +#endif cifs_destroy_request_bufs(); cifs_destroy_mids(); cifs_destroy_inodecache(); diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index bd183ec17066..60c202bbcdfc 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 b4fed69add86..92a7d028cb78 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; @@ -3261,25 +3265,6 @@ cifs_match_super(struct super_block *sb, void *data) 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]; @@ -3930,8 +3915,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 @@ -3946,8 +3932,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) @@ -3960,17 +3945,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); @@ -3978,7 +3961,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 patchwork Thu Nov 15 14:20:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998369 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk8c4zkKz9s3q for ; Fri, 16 Nov 2018 01:22:24 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387922AbeKPAa0 (ORCPT ); Thu, 15 Nov 2018 19:30:26 -0500 Received: from mx2.suse.de ([195.135.220.15]:42824 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1732953AbeKPAaZ (ORCPT ); Thu, 15 Nov 2018 19:30:25 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 2EC7CAD70; Thu, 15 Nov 2018 14:22:21 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 10/14] cifs: Add support for failover in cifs_mount() Date: Thu, 15 Nov 2018 15:20:59 +0100 Message-Id: <20181115142103.24617-11-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara This patch adds support for failover when failing to connect in cifs_mount(). Signed-off-by: Paulo Alcantara Signed-off-by: Aurelien Aptel --- fs/cifs/cifs_dfs_ref.c | 20 +++-- fs/cifs/connect.c | 215 ++++++++++++++++++++++++++++++++++++++++++++++--- fs/cifs/misc.c | 3 + 3 files changed, 224 insertions(+), 14 deletions(-) diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c index 6e6953f35db2..f98e3ffa18e9 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 92a7d028cb78..c4ce52818ed6 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3890,10 +3890,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); @@ -3938,7 +3939,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); @@ -3970,6 +3971,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 @@ -4122,22 +4260,36 @@ 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) 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 @@ -4162,8 +4314,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); @@ -4181,8 +4361,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; @@ -4193,11 +4381,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; @@ -4213,6 +4408,8 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) 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 5e315e4009e2..1e1bf1759247 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -140,6 +140,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 patchwork Thu Nov 15 14:21:00 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998370 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk8l0v1jz9s3q for ; Fri, 16 Nov 2018 01:22:31 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388097AbeKPAab (ORCPT ); Thu, 15 Nov 2018 19:30:31 -0500 Received: from mx2.suse.de ([195.135.220.15]:42840 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1732953AbeKPAab (ORCPT ); Thu, 15 Nov 2018 19:30:31 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay1.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 14E8CAD70; Thu, 15 Nov 2018 14:22:27 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 11/14] cifs: Add support for failover in cifs_reconnect() Date: Thu, 15 Nov 2018 15:21:00 +0100 Message-Id: <20181115142103.24617-12-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara After failing to reconnect to original target, it will retry any target available from DFS cache. Signed-off-by: Paulo Alcantara Signed-off-by: Aurelien Aptel --- 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 60c202bbcdfc..643633a3dc09 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 c4ce52818ed6..52d837d7bf57 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 @@ -321,6 +322,115 @@ 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->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 @@ -339,8 +449,34 @@ 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); +#ifdef CONFIG_CIFS_DFS_UPCALL + cifs_sb = find_super_by_tcp(server); + if (IS_ERR(cifs_sb)) { + rc = PTR_ERR(cifs_sb); + } 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__); + } + } + if (!rc) { + server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list); + } else { + server->nr_targets = 1; + cifs_dbg(VFS, "%s: will not do DFS failover: rc = %d\n", + __func__, rc); + } + 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 */ @@ -414,14 +550,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 { @@ -434,6 +578,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); @@ -2470,6 +2630,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info) } 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); @@ -4291,6 +4453,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 @@ -4397,6 +4565,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 @@ -4638,6 +4822,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 patchwork Thu Nov 15 14:21:01 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998371 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk8m3v73z9s3q for ; Fri, 16 Nov 2018 01:22:32 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388179AbeKPAad (ORCPT ); Thu, 15 Nov 2018 19:30:33 -0500 Received: from mx2.suse.de ([195.135.220.15]:42892 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1732953AbeKPAad (ORCPT ); Thu, 15 Nov 2018 19:30:33 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 92544AF3E; Thu, 15 Nov 2018 14:22:30 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 12/14] cifs: start DFS cache refresher in cifs_mount() Date: Thu, 15 Nov 2018 15:21:01 +0100 Message-Id: <20181115142103.24617-13-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara Start the DFS cache refresh worker per volume during cifs mount. Signed-off-by: Paulo Alcantara Signed-off-by: Aurelien Aptel --- fs/cifs/connect.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 52d837d7bf57..93fd8cbe0e08 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -4581,6 +4581,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 @@ -4823,6 +4828,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 patchwork Thu Nov 15 14:21:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998372 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk8s5kf7z9s3q for ; Fri, 16 Nov 2018 01:22:37 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388226AbeKPAaj (ORCPT ); Thu, 15 Nov 2018 19:30:39 -0500 Received: from mx2.suse.de ([195.135.220.15]:42920 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1732953AbeKPAaj (ORCPT ); Thu, 15 Nov 2018 19:30:39 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 53234AF3E; Thu, 15 Nov 2018 14:22:35 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 13/14] cifs: Add support for failover in smb2_reconnect() Date: Thu, 15 Nov 2018 15:21:02 +0100 Message-Id: <20181115142103.24617-14-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara 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 --- 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 1e1bf1759247..113980dba4d8 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -946,3 +946,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 27f86537a5d1..e360d84b1b4f 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 @@ smb2_hdr_assemble(struct smb2_sync_hdr *shdr, __le16 smb2_cmd, 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 patchwork Thu Nov 15 14:21:03 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Aur=C3=A9lien_Aptel?= X-Patchwork-Id: 998373 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-cifs-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42wk8z3J5Kz9s3q for ; Fri, 16 Nov 2018 01:22:43 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1732953AbeKPAao (ORCPT ); Thu, 15 Nov 2018 19:30:44 -0500 Received: from mx2.suse.de ([195.135.220.15]:42960 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1729035AbeKPAao (ORCPT ); Thu, 15 Nov 2018 19:30:44 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 8FDCAAFF1; Thu, 15 Nov 2018 14:22:40 +0000 (UTC) From: Aurelien Aptel To: linux-cifs@vger.kernel.org Cc: smfrench@gmail.com, Paulo Alcantara , Paulo Alcantara , Aurelien Aptel Subject: [PATCH v1 14/14] cifs: Add support for failover in cifs_reconnect_tcon() Date: Thu, 15 Nov 2018 15:21:03 +0100 Message-Id: <20181115142103.24617-15-aaptel@suse.com> X-Mailer: git-send-email 2.13.7 In-Reply-To: <20181115142103.24617-1-aaptel@suse.com> References: <20181115142103.24617-1-aaptel@suse.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org From: Paulo Alcantara 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 Signed-off-by: Aurelien Aptel --- 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);