mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-25 13:14:07 +08:00
cifs: Add support for failover in cifs_reconnect()
After failing to reconnect to original target, it will retry any target available from DFS cache. Signed-off-by: Paulo Alcantara <palcantara@suse.de> Reviewed-by: Aurelien Aptel <aaptel@suse.com> Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
parent
4a367dc044
commit
93d5cb517d
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -57,6 +57,7 @@
|
||||
#include "smb2proto.h"
|
||||
#include "smbdirect.h"
|
||||
#include "dns_resolve.h"
|
||||
#include "cifsfs.h"
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
#include "dfs_cache.h"
|
||||
#endif
|
||||
@ -322,6 +323,116 @@ static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
|
||||
static void cifs_prune_tlinks(struct work_struct *work);
|
||||
static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
|
||||
const char *devname, bool is_smb3);
|
||||
static char *extract_hostname(const char *unc);
|
||||
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
struct super_cb_data {
|
||||
struct TCP_Server_Info *server;
|
||||
struct cifs_sb_info *cifs_sb;
|
||||
};
|
||||
|
||||
/* These functions must be called with server->srv_mutex held */
|
||||
|
||||
static void super_cb(struct super_block *sb, void *arg)
|
||||
{
|
||||
struct super_cb_data *d = arg;
|
||||
struct cifs_sb_info *cifs_sb;
|
||||
struct cifs_tcon *tcon;
|
||||
|
||||
if (d->cifs_sb)
|
||||
return;
|
||||
|
||||
cifs_sb = CIFS_SB(sb);
|
||||
tcon = cifs_sb_master_tcon(cifs_sb);
|
||||
if (tcon->ses->server == d->server)
|
||||
d->cifs_sb = cifs_sb;
|
||||
}
|
||||
|
||||
static inline struct cifs_sb_info *
|
||||
find_super_by_tcp(struct TCP_Server_Info *server)
|
||||
{
|
||||
struct super_cb_data d = {
|
||||
.server = server,
|
||||
.cifs_sb = NULL,
|
||||
};
|
||||
|
||||
iterate_supers_type(&cifs_fs_type, super_cb, &d);
|
||||
return d.cifs_sb ? d.cifs_sb : ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
static void reconn_inval_dfs_target(struct TCP_Server_Info *server,
|
||||
struct cifs_sb_info *cifs_sb,
|
||||
struct dfs_cache_tgt_list *tgt_list,
|
||||
struct dfs_cache_tgt_iterator **tgt_it)
|
||||
{
|
||||
const char *name;
|
||||
int rc;
|
||||
char *ipaddr = NULL;
|
||||
char *unc;
|
||||
int len;
|
||||
|
||||
if (!cifs_sb || !cifs_sb->origin_fullpath || !tgt_list ||
|
||||
!server->nr_targets)
|
||||
return;
|
||||
|
||||
if (!*tgt_it) {
|
||||
*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
|
||||
} else {
|
||||
*tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
|
||||
if (!*tgt_it)
|
||||
*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
|
||||
}
|
||||
|
||||
cifs_dbg(FYI, "%s: UNC: %s\n", __func__, cifs_sb->origin_fullpath);
|
||||
|
||||
name = dfs_cache_get_tgt_name(*tgt_it);
|
||||
|
||||
kfree(server->hostname);
|
||||
|
||||
server->hostname = extract_hostname(name);
|
||||
if (!server->hostname) {
|
||||
cifs_dbg(FYI, "%s: failed to extract hostname from target: %d\n",
|
||||
__func__, -ENOMEM);
|
||||
return;
|
||||
}
|
||||
|
||||
len = strlen(server->hostname) + 3;
|
||||
|
||||
unc = kmalloc(len, GFP_KERNEL);
|
||||
if (!unc) {
|
||||
cifs_dbg(FYI, "%s: failed to create UNC path\n", __func__);
|
||||
return;
|
||||
}
|
||||
snprintf(unc, len, "\\\\%s", server->hostname);
|
||||
|
||||
rc = dns_resolve_server_name_to_ip(unc, &ipaddr);
|
||||
kfree(unc);
|
||||
|
||||
if (rc < 0) {
|
||||
cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n",
|
||||
__func__, server->hostname, rc);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = cifs_convert_address((struct sockaddr *)&server->dstaddr, ipaddr,
|
||||
strlen(ipaddr));
|
||||
kfree(ipaddr);
|
||||
|
||||
if (!rc) {
|
||||
cifs_dbg(FYI, "%s: failed to get ipaddr out of hostname\n",
|
||||
__func__);
|
||||
}
|
||||
}
|
||||
|
||||
static inline int reconn_setup_dfs_targets(struct cifs_sb_info *cifs_sb,
|
||||
struct dfs_cache_tgt_list *tl,
|
||||
struct dfs_cache_tgt_iterator **it)
|
||||
{
|
||||
if (!cifs_sb->origin_fullpath)
|
||||
return -EOPNOTSUPP;
|
||||
return dfs_cache_noreq_find(cifs_sb->origin_fullpath + 1, NULL, tl);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* cifs tcp session reconnection
|
||||
@ -340,8 +451,33 @@ cifs_reconnect(struct TCP_Server_Info *server)
|
||||
struct cifs_tcon *tcon;
|
||||
struct mid_q_entry *mid_entry;
|
||||
struct list_head retry_list;
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
struct cifs_sb_info *cifs_sb;
|
||||
struct dfs_cache_tgt_list tgt_list;
|
||||
struct dfs_cache_tgt_iterator *tgt_it = NULL;
|
||||
#endif
|
||||
|
||||
spin_lock(&GlobalMid_Lock);
|
||||
server->nr_targets = 1;
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
cifs_sb = find_super_by_tcp(server);
|
||||
if (IS_ERR(cifs_sb)) {
|
||||
rc = PTR_ERR(cifs_sb);
|
||||
cifs_dbg(FYI, "%s: will not do DFS failover: rc = %d\n",
|
||||
__func__, rc);
|
||||
cifs_sb = NULL;
|
||||
} else {
|
||||
rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list, &tgt_it);
|
||||
if (rc) {
|
||||
cifs_dbg(VFS, "%s: no target servers for DFS failover\n",
|
||||
__func__);
|
||||
} else {
|
||||
server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list);
|
||||
}
|
||||
}
|
||||
cifs_dbg(FYI, "%s: will retry %d target(s)\n", __func__,
|
||||
server->nr_targets);
|
||||
#endif
|
||||
if (server->tcpStatus == CifsExiting) {
|
||||
/* the demux thread will exit normally
|
||||
next time through the loop */
|
||||
@ -415,14 +551,22 @@ cifs_reconnect(struct TCP_Server_Info *server)
|
||||
do {
|
||||
try_to_freeze();
|
||||
|
||||
/* we should try only the port we connected to before */
|
||||
mutex_lock(&server->srv_mutex);
|
||||
/*
|
||||
* Set up next DFS target server (if any) for reconnect. If DFS
|
||||
* feature is disabled, then we will retry last server we
|
||||
* connected to before.
|
||||
*/
|
||||
if (cifs_rdma_enabled(server))
|
||||
rc = smbd_reconnect(server);
|
||||
else
|
||||
rc = generic_ip_connect(server);
|
||||
if (rc) {
|
||||
cifs_dbg(FYI, "reconnect error %d\n", rc);
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
reconn_inval_dfs_target(server, cifs_sb, &tgt_list,
|
||||
&tgt_it);
|
||||
#endif
|
||||
mutex_unlock(&server->srv_mutex);
|
||||
msleep(3000);
|
||||
} else {
|
||||
@ -435,6 +579,22 @@ cifs_reconnect(struct TCP_Server_Info *server)
|
||||
}
|
||||
} while (server->tcpStatus == CifsNeedReconnect);
|
||||
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
if (tgt_it) {
|
||||
rc = dfs_cache_noreq_update_tgthint(cifs_sb->origin_fullpath + 1,
|
||||
tgt_it);
|
||||
if (rc) {
|
||||
cifs_dbg(VFS, "%s: failed to update DFS target hint: rc = %d\n",
|
||||
__func__, rc);
|
||||
}
|
||||
rc = dfs_cache_update_vol(cifs_sb->origin_fullpath, server);
|
||||
if (rc) {
|
||||
cifs_dbg(VFS, "%s: failed to update vol info in DFS cache: rc = %d\n",
|
||||
__func__, rc);
|
||||
}
|
||||
}
|
||||
dfs_cache_free_tgts(&tgt_list);
|
||||
#endif
|
||||
if (server->tcpStatus == CifsNeedNegotiate)
|
||||
mod_delayed_work(cifsiod_wq, &server->echo, 0);
|
||||
|
||||
@ -2471,6 +2631,8 @@ smbd_connected:
|
||||
}
|
||||
tcp_ses->tcpStatus = CifsNeedNegotiate;
|
||||
|
||||
tcp_ses->nr_targets = 1;
|
||||
|
||||
/* thread spawned, put it on the list */
|
||||
spin_lock(&cifs_tcp_ses_lock);
|
||||
list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
|
||||
@ -4303,6 +4465,12 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
|
||||
goto error;
|
||||
}
|
||||
|
||||
full_path = build_unc_path_to_root(vol, cifs_sb, true);
|
||||
if (IS_ERR(full_path)) {
|
||||
rc = PTR_ERR(full_path);
|
||||
full_path = NULL;
|
||||
goto error;
|
||||
}
|
||||
/*
|
||||
* Perform an unconditional check for whether there are DFS
|
||||
* referrals for this path without prefix, to provide support
|
||||
@ -4409,6 +4577,22 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
spin_lock(&cifs_tcp_ses_lock);
|
||||
if (!tcon->dfs_path) {
|
||||
/* Save full path in new tcon to do failover when reconnecting tcons */
|
||||
tcon->dfs_path = full_path;
|
||||
full_path = NULL;
|
||||
tcon->remap = cifs_remap(cifs_sb);
|
||||
}
|
||||
cifs_sb->origin_fullpath = kstrndup(tcon->dfs_path,
|
||||
strlen(tcon->dfs_path), GFP_KERNEL);
|
||||
if (!cifs_sb->origin_fullpath) {
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
rc = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
|
||||
/*
|
||||
* After reconnecting to a different server, unique ids won't
|
||||
* match anymore, so we disable serverino. This prevents
|
||||
@ -4650,6 +4834,9 @@ cifs_umount(struct cifs_sb_info *cifs_sb)
|
||||
|
||||
kfree(cifs_sb->mountdata);
|
||||
kfree(cifs_sb->prepath);
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
kfree(cifs_sb->origin_fullpath);
|
||||
#endif
|
||||
call_rcu(&cifs_sb->rcu, delayed_free);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user