mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 21:21:41 +00:00
cifs: Add support for failover in cifs_mount()
This patch adds support for failover when failing to connect in cifs_mount(). Signed-off-by: Paulo Alcantara <palcantara@suse.de> Reviewed-by: Aurelien Aptel <aaptel@suse.com> Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
parent
5a650501eb
commit
4a367dc044
@ -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)
|
||||
|
@ -3891,10 +3891,11 @@ static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
|
||||
*/
|
||||
static char *
|
||||
build_unc_path_to_root(const struct smb_vol *vol,
|
||||
const struct cifs_sb_info *cifs_sb)
|
||||
const struct cifs_sb_info *cifs_sb, bool useppath)
|
||||
{
|
||||
char *full_path, *pos;
|
||||
unsigned int pplen = vol->prepath ? strlen(vol->prepath) + 1 : 0;
|
||||
unsigned int pplen = useppath && vol->prepath ?
|
||||
strlen(vol->prepath) + 1 : 0;
|
||||
unsigned int unc_len = strnlen(vol->UNC, MAX_TREE_SIZE + 1);
|
||||
|
||||
full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
|
||||
@ -3939,7 +3940,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
|
||||
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
|
||||
return -EREMOTE;
|
||||
|
||||
full_path = build_unc_path_to_root(volume_info, cifs_sb);
|
||||
full_path = build_unc_path_to_root(volume_info, cifs_sb, true);
|
||||
if (IS_ERR(full_path))
|
||||
return PTR_ERR(full_path);
|
||||
|
||||
@ -3971,6 +3972,143 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
|
||||
kfree(full_path);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static inline int get_next_dfs_tgt(const char *path,
|
||||
struct dfs_cache_tgt_list *tgt_list,
|
||||
struct dfs_cache_tgt_iterator **tgt_it)
|
||||
{
|
||||
if (!*tgt_it)
|
||||
*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
|
||||
else
|
||||
*tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
|
||||
return !*tgt_it ? -EHOSTDOWN : 0;
|
||||
}
|
||||
|
||||
static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it,
|
||||
struct smb_vol *fake_vol, struct smb_vol *vol)
|
||||
{
|
||||
const char *tgt = dfs_cache_get_tgt_name(tgt_it);
|
||||
int len = strlen(tgt) + 2;
|
||||
char *new_unc;
|
||||
|
||||
new_unc = kmalloc(len, GFP_KERNEL);
|
||||
if (!new_unc)
|
||||
return -ENOMEM;
|
||||
snprintf(new_unc, len, "\\%s", tgt);
|
||||
|
||||
kfree(vol->UNC);
|
||||
vol->UNC = new_unc;
|
||||
|
||||
if (fake_vol->prepath) {
|
||||
kfree(vol->prepath);
|
||||
vol->prepath = fake_vol->prepath;
|
||||
fake_vol->prepath = NULL;
|
||||
}
|
||||
memcpy(&vol->dstaddr, &fake_vol->dstaddr, sizeof(vol->dstaddr));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_dfs_tgt_conn(const char *path,
|
||||
const struct dfs_cache_tgt_iterator *tgt_it,
|
||||
struct cifs_sb_info *cifs_sb,
|
||||
struct smb_vol *vol,
|
||||
unsigned int *xid,
|
||||
struct TCP_Server_Info **server,
|
||||
struct cifs_ses **ses,
|
||||
struct cifs_tcon **tcon)
|
||||
{
|
||||
int rc;
|
||||
struct dfs_info3_param ref = {0};
|
||||
char *mdata = NULL, *fake_devname = NULL;
|
||||
struct smb_vol fake_vol = {0};
|
||||
|
||||
cifs_dbg(FYI, "%s: dfs path: %s\n", __func__, path);
|
||||
|
||||
rc = dfs_cache_get_tgt_referral(path, tgt_it, &ref);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
mdata = cifs_compose_mount_options(cifs_sb->mountdata, path, &ref,
|
||||
&fake_devname);
|
||||
free_dfs_info_param(&ref);
|
||||
|
||||
if (IS_ERR(mdata)) {
|
||||
rc = PTR_ERR(mdata);
|
||||
mdata = NULL;
|
||||
} else {
|
||||
cifs_dbg(FYI, "%s: fake_devname: %s\n", __func__, fake_devname);
|
||||
rc = cifs_setup_volume_info(&fake_vol, mdata, fake_devname,
|
||||
false);
|
||||
}
|
||||
kfree(mdata);
|
||||
kfree(fake_devname);
|
||||
|
||||
if (!rc) {
|
||||
/*
|
||||
* We use a 'fake_vol' here because we need pass it down to the
|
||||
* mount_{get,put} functions to test connection against new DFS
|
||||
* targets.
|
||||
*/
|
||||
mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon);
|
||||
rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses,
|
||||
tcon);
|
||||
if (!rc) {
|
||||
/*
|
||||
* We were able to connect to new target server.
|
||||
* Update current volume info with new target server.
|
||||
*/
|
||||
rc = update_vol_info(tgt_it, &fake_vol, vol);
|
||||
}
|
||||
}
|
||||
cifs_cleanup_volume_info_contents(&fake_vol);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mount_do_dfs_failover(const char *path,
|
||||
struct cifs_sb_info *cifs_sb,
|
||||
struct smb_vol *vol,
|
||||
struct cifs_ses *root_ses,
|
||||
unsigned int *xid,
|
||||
struct TCP_Server_Info **server,
|
||||
struct cifs_ses **ses,
|
||||
struct cifs_tcon **tcon)
|
||||
{
|
||||
int rc;
|
||||
struct dfs_cache_tgt_list tgt_list;
|
||||
struct dfs_cache_tgt_iterator *tgt_it = NULL;
|
||||
|
||||
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
rc = dfs_cache_noreq_find(path, NULL, &tgt_list);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
for (;;) {
|
||||
/* Get next DFS target server - if any */
|
||||
rc = get_next_dfs_tgt(path, &tgt_list, &tgt_it);
|
||||
if (rc)
|
||||
break;
|
||||
/* Connect to next DFS target */
|
||||
rc = setup_dfs_tgt_conn(path, tgt_it, cifs_sb, vol, xid, server,
|
||||
ses, tcon);
|
||||
if (!rc || rc == -EACCES || rc == -EOPNOTSUPP)
|
||||
break;
|
||||
}
|
||||
if (!rc) {
|
||||
/*
|
||||
* Update DFS target hint in DFS referral cache with the target
|
||||
* server we successfully reconnected to.
|
||||
*/
|
||||
rc = dfs_cache_update_tgthint(*xid, root_ses ? root_ses : *ses,
|
||||
cifs_sb->local_nls,
|
||||
cifs_remap(cifs_sb), path,
|
||||
tgt_it);
|
||||
}
|
||||
dfs_cache_free_tgts(&tgt_list);
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
@ -4123,22 +4261,47 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
|
||||
int rc = 0;
|
||||
unsigned int xid;
|
||||
struct cifs_ses *ses;
|
||||
struct cifs_tcon *root_tcon = NULL;
|
||||
struct cifs_tcon *tcon = NULL;
|
||||
struct TCP_Server_Info *server;
|
||||
char *root_path = NULL, *full_path = NULL;
|
||||
char *old_mountdata;
|
||||
int count;
|
||||
|
||||
rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
|
||||
if (!rc && tcon) {
|
||||
rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
|
||||
if (!rc)
|
||||
goto out;
|
||||
if (rc != -EREMOTE)
|
||||
goto error;
|
||||
/* If not a standalone DFS root, then check if path is remote */
|
||||
rc = dfs_cache_find(xid, ses, cifs_sb->local_nls,
|
||||
cifs_remap(cifs_sb), vol->UNC + 1, NULL,
|
||||
NULL);
|
||||
if (rc) {
|
||||
rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
|
||||
if (!rc)
|
||||
goto out;
|
||||
if (rc != -EREMOTE)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if ((rc == -EACCES) || (rc == -EOPNOTSUPP) || (ses == NULL) || (server == NULL))
|
||||
/*
|
||||
* If first DFS target server went offline and we failed to connect it,
|
||||
* server and ses pointers are NULL at this point, though we still have
|
||||
* chance to get a cached DFS referral in expand_dfs_referral() and
|
||||
* retry next target available in it.
|
||||
*
|
||||
* If a NULL ses ptr is passed to dfs_cache_find(), a lookup will be
|
||||
* performed against DFS path and *no* requests will be sent to server
|
||||
* for any new DFS referrals. Hence it's safe to skip checking whether
|
||||
* server or ses ptr is NULL.
|
||||
*/
|
||||
if (rc == -EACCES || rc == -EOPNOTSUPP)
|
||||
goto error;
|
||||
|
||||
root_path = build_unc_path_to_root(vol, cifs_sb, false);
|
||||
if (IS_ERR(root_path)) {
|
||||
rc = PTR_ERR(root_path);
|
||||
root_path = NULL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform an unconditional check for whether there are DFS
|
||||
@ -4163,8 +4326,36 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
|
||||
if (rc) {
|
||||
if (rc == -EACCES || rc == -EOPNOTSUPP)
|
||||
goto error;
|
||||
/* Perform DFS failover to any other DFS targets */
|
||||
rc = mount_do_dfs_failover(root_path + 1, cifs_sb, vol, NULL,
|
||||
&xid, &server, &ses, &tcon);
|
||||
if (rc)
|
||||
goto error;
|
||||
}
|
||||
|
||||
kfree(root_path);
|
||||
root_path = build_unc_path_to_root(vol, cifs_sb, false);
|
||||
if (IS_ERR(root_path)) {
|
||||
rc = PTR_ERR(root_path);
|
||||
root_path = NULL;
|
||||
goto error;
|
||||
}
|
||||
/* Cache out resolved root server */
|
||||
(void)dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
|
||||
root_path + 1, NULL, NULL);
|
||||
/*
|
||||
* Save root tcon for additional DFS requests to update or create a new
|
||||
* DFS cache entry, or even perform DFS failover.
|
||||
*/
|
||||
spin_lock(&cifs_tcp_ses_lock);
|
||||
tcon->tc_count++;
|
||||
tcon->dfs_path = root_path;
|
||||
root_path = NULL;
|
||||
tcon->remap = cifs_remap(cifs_sb);
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
|
||||
root_tcon = tcon;
|
||||
|
||||
for (count = 1; ;) {
|
||||
if (!rc && tcon) {
|
||||
rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
|
||||
@ -4182,8 +4373,16 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(full_path);
|
||||
full_path = build_unc_path_to_root(vol, cifs_sb, true);
|
||||
if (IS_ERR(full_path)) {
|
||||
rc = PTR_ERR(full_path);
|
||||
full_path = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
old_mountdata = cifs_sb->mountdata;
|
||||
rc = expand_dfs_referral(xid, tcon->ses, vol, cifs_sb,
|
||||
rc = expand_dfs_referral(xid, root_tcon->ses, vol, cifs_sb,
|
||||
true);
|
||||
if (rc)
|
||||
break;
|
||||
@ -4194,11 +4393,18 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
|
||||
&tcon);
|
||||
}
|
||||
if (rc) {
|
||||
if (rc == -EACCES || rc == -EOPNOTSUPP)
|
||||
break;
|
||||
/* Perform DFS failover to any other DFS targets */
|
||||
rc = mount_do_dfs_failover(full_path + 1, cifs_sb, vol,
|
||||
root_tcon->ses, &xid,
|
||||
&server, &ses, &tcon);
|
||||
if (rc == -EACCES || rc == -EOPNOTSUPP || !server ||
|
||||
!ses)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
cifs_put_tcon(root_tcon);
|
||||
|
||||
if (rc)
|
||||
goto error;
|
||||
@ -4214,6 +4420,8 @@ out:
|
||||
return mount_setup_tlink(cifs_sb, ses, tcon);
|
||||
|
||||
error:
|
||||
kfree(full_path);
|
||||
kfree(root_path);
|
||||
mount_put_conns(cifs_sb, xid, server, ses, tcon);
|
||||
return rc;
|
||||
}
|
||||
|
@ -146,6 +146,9 @@ tconInfoFree(struct cifs_tcon *buf_to_free)
|
||||
kfree(buf_to_free->nativeFileSystem);
|
||||
kzfree(buf_to_free->password);
|
||||
kfree(buf_to_free->crfid.fid);
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
kfree(buf_to_free->dfs_path);
|
||||
#endif
|
||||
kfree(buf_to_free);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user