Skip to content

Commit 4a367dc

Browse files
Paulo AlcantaraSteve French
authored andcommitted
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 <[email protected]> Reviewed-by: Aurelien Aptel <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent 5a65050 commit 4a367dc

File tree

3 files changed

+236
-15
lines changed

3 files changed

+236
-15
lines changed

fs/cifs/cifs_dfs_ref.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,20 +255,30 @@ static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt,
255255
{
256256
struct vfsmount *mnt;
257257
char *mountdata;
258-
char *devname = NULL;
258+
char *devname;
259+
260+
/*
261+
* Always pass down the DFS full path to smb3_do_mount() so we
262+
* can use it later for failover.
263+
*/
264+
devname = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL);
265+
if (!devname)
266+
return ERR_PTR(-ENOMEM);
267+
268+
convert_delimiter(devname, '/');
259269

260270
/* strip first '\' from fullpath */
261271
mountdata = cifs_compose_mount_options(cifs_sb->mountdata,
262-
fullpath + 1, ref, &devname);
263-
264-
if (IS_ERR(mountdata))
272+
fullpath + 1, ref, NULL);
273+
if (IS_ERR(mountdata)) {
274+
kfree(devname);
265275
return (struct vfsmount *)mountdata;
276+
}
266277

267278
mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata);
268279
kfree(mountdata);
269280
kfree(devname);
270281
return mnt;
271-
272282
}
273283

274284
static void dump_referral(const struct dfs_info3_param *ref)

fs/cifs/connect.c

Lines changed: 218 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3891,10 +3891,11 @@ static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
38913891
*/
38923892
static char *
38933893
build_unc_path_to_root(const struct smb_vol *vol,
3894-
const struct cifs_sb_info *cifs_sb)
3894+
const struct cifs_sb_info *cifs_sb, bool useppath)
38953895
{
38963896
char *full_path, *pos;
3897-
unsigned int pplen = vol->prepath ? strlen(vol->prepath) + 1 : 0;
3897+
unsigned int pplen = useppath && vol->prepath ?
3898+
strlen(vol->prepath) + 1 : 0;
38983899
unsigned int unc_len = strnlen(vol->UNC, MAX_TREE_SIZE + 1);
38993900

39003901
full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
@@ -3939,7 +3940,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
39393940
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
39403941
return -EREMOTE;
39413942

3942-
full_path = build_unc_path_to_root(volume_info, cifs_sb);
3943+
full_path = build_unc_path_to_root(volume_info, cifs_sb, true);
39433944
if (IS_ERR(full_path))
39443945
return PTR_ERR(full_path);
39453946

@@ -3971,6 +3972,143 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
39713972
kfree(full_path);
39723973
return rc;
39733974
}
3975+
3976+
static inline int get_next_dfs_tgt(const char *path,
3977+
struct dfs_cache_tgt_list *tgt_list,
3978+
struct dfs_cache_tgt_iterator **tgt_it)
3979+
{
3980+
if (!*tgt_it)
3981+
*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
3982+
else
3983+
*tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
3984+
return !*tgt_it ? -EHOSTDOWN : 0;
3985+
}
3986+
3987+
static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it,
3988+
struct smb_vol *fake_vol, struct smb_vol *vol)
3989+
{
3990+
const char *tgt = dfs_cache_get_tgt_name(tgt_it);
3991+
int len = strlen(tgt) + 2;
3992+
char *new_unc;
3993+
3994+
new_unc = kmalloc(len, GFP_KERNEL);
3995+
if (!new_unc)
3996+
return -ENOMEM;
3997+
snprintf(new_unc, len, "\\%s", tgt);
3998+
3999+
kfree(vol->UNC);
4000+
vol->UNC = new_unc;
4001+
4002+
if (fake_vol->prepath) {
4003+
kfree(vol->prepath);
4004+
vol->prepath = fake_vol->prepath;
4005+
fake_vol->prepath = NULL;
4006+
}
4007+
memcpy(&vol->dstaddr, &fake_vol->dstaddr, sizeof(vol->dstaddr));
4008+
4009+
return 0;
4010+
}
4011+
4012+
static int setup_dfs_tgt_conn(const char *path,
4013+
const struct dfs_cache_tgt_iterator *tgt_it,
4014+
struct cifs_sb_info *cifs_sb,
4015+
struct smb_vol *vol,
4016+
unsigned int *xid,
4017+
struct TCP_Server_Info **server,
4018+
struct cifs_ses **ses,
4019+
struct cifs_tcon **tcon)
4020+
{
4021+
int rc;
4022+
struct dfs_info3_param ref = {0};
4023+
char *mdata = NULL, *fake_devname = NULL;
4024+
struct smb_vol fake_vol = {0};
4025+
4026+
cifs_dbg(FYI, "%s: dfs path: %s\n", __func__, path);
4027+
4028+
rc = dfs_cache_get_tgt_referral(path, tgt_it, &ref);
4029+
if (rc)
4030+
return rc;
4031+
4032+
mdata = cifs_compose_mount_options(cifs_sb->mountdata, path, &ref,
4033+
&fake_devname);
4034+
free_dfs_info_param(&ref);
4035+
4036+
if (IS_ERR(mdata)) {
4037+
rc = PTR_ERR(mdata);
4038+
mdata = NULL;
4039+
} else {
4040+
cifs_dbg(FYI, "%s: fake_devname: %s\n", __func__, fake_devname);
4041+
rc = cifs_setup_volume_info(&fake_vol, mdata, fake_devname,
4042+
false);
4043+
}
4044+
kfree(mdata);
4045+
kfree(fake_devname);
4046+
4047+
if (!rc) {
4048+
/*
4049+
* We use a 'fake_vol' here because we need pass it down to the
4050+
* mount_{get,put} functions to test connection against new DFS
4051+
* targets.
4052+
*/
4053+
mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon);
4054+
rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses,
4055+
tcon);
4056+
if (!rc) {
4057+
/*
4058+
* We were able to connect to new target server.
4059+
* Update current volume info with new target server.
4060+
*/
4061+
rc = update_vol_info(tgt_it, &fake_vol, vol);
4062+
}
4063+
}
4064+
cifs_cleanup_volume_info_contents(&fake_vol);
4065+
return rc;
4066+
}
4067+
4068+
static int mount_do_dfs_failover(const char *path,
4069+
struct cifs_sb_info *cifs_sb,
4070+
struct smb_vol *vol,
4071+
struct cifs_ses *root_ses,
4072+
unsigned int *xid,
4073+
struct TCP_Server_Info **server,
4074+
struct cifs_ses **ses,
4075+
struct cifs_tcon **tcon)
4076+
{
4077+
int rc;
4078+
struct dfs_cache_tgt_list tgt_list;
4079+
struct dfs_cache_tgt_iterator *tgt_it = NULL;
4080+
4081+
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
4082+
return -EOPNOTSUPP;
4083+
4084+
rc = dfs_cache_noreq_find(path, NULL, &tgt_list);
4085+
if (rc)
4086+
return rc;
4087+
4088+
for (;;) {
4089+
/* Get next DFS target server - if any */
4090+
rc = get_next_dfs_tgt(path, &tgt_list, &tgt_it);
4091+
if (rc)
4092+
break;
4093+
/* Connect to next DFS target */
4094+
rc = setup_dfs_tgt_conn(path, tgt_it, cifs_sb, vol, xid, server,
4095+
ses, tcon);
4096+
if (!rc || rc == -EACCES || rc == -EOPNOTSUPP)
4097+
break;
4098+
}
4099+
if (!rc) {
4100+
/*
4101+
* Update DFS target hint in DFS referral cache with the target
4102+
* server we successfully reconnected to.
4103+
*/
4104+
rc = dfs_cache_update_tgthint(*xid, root_ses ? root_ses : *ses,
4105+
cifs_sb->local_nls,
4106+
cifs_remap(cifs_sb), path,
4107+
tgt_it);
4108+
}
4109+
dfs_cache_free_tgts(&tgt_list);
4110+
return rc;
4111+
}
39744112
#endif
39754113

39764114
static int
@@ -4123,22 +4261,47 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
41234261
int rc = 0;
41244262
unsigned int xid;
41254263
struct cifs_ses *ses;
4264+
struct cifs_tcon *root_tcon = NULL;
41264265
struct cifs_tcon *tcon = NULL;
41274266
struct TCP_Server_Info *server;
4267+
char *root_path = NULL, *full_path = NULL;
41284268
char *old_mountdata;
41294269
int count;
41304270

41314271
rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
41324272
if (!rc && tcon) {
4133-
rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
4134-
if (!rc)
4135-
goto out;
4136-
if (rc != -EREMOTE)
4137-
goto error;
4273+
/* If not a standalone DFS root, then check if path is remote */
4274+
rc = dfs_cache_find(xid, ses, cifs_sb->local_nls,
4275+
cifs_remap(cifs_sb), vol->UNC + 1, NULL,
4276+
NULL);
4277+
if (rc) {
4278+
rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
4279+
if (!rc)
4280+
goto out;
4281+
if (rc != -EREMOTE)
4282+
goto error;
4283+
}
41384284
}
4139-
if ((rc == -EACCES) || (rc == -EOPNOTSUPP) || (ses == NULL) || (server == NULL))
4285+
/*
4286+
* If first DFS target server went offline and we failed to connect it,
4287+
* server and ses pointers are NULL at this point, though we still have
4288+
* chance to get a cached DFS referral in expand_dfs_referral() and
4289+
* retry next target available in it.
4290+
*
4291+
* If a NULL ses ptr is passed to dfs_cache_find(), a lookup will be
4292+
* performed against DFS path and *no* requests will be sent to server
4293+
* for any new DFS referrals. Hence it's safe to skip checking whether
4294+
* server or ses ptr is NULL.
4295+
*/
4296+
if (rc == -EACCES || rc == -EOPNOTSUPP)
41404297
goto error;
41414298

4299+
root_path = build_unc_path_to_root(vol, cifs_sb, false);
4300+
if (IS_ERR(root_path)) {
4301+
rc = PTR_ERR(root_path);
4302+
root_path = NULL;
4303+
goto error;
4304+
}
41424305

41434306
/*
41444307
* 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)
41634326
if (rc) {
41644327
if (rc == -EACCES || rc == -EOPNOTSUPP)
41654328
goto error;
4329+
/* Perform DFS failover to any other DFS targets */
4330+
rc = mount_do_dfs_failover(root_path + 1, cifs_sb, vol, NULL,
4331+
&xid, &server, &ses, &tcon);
4332+
if (rc)
4333+
goto error;
41664334
}
41674335

4336+
kfree(root_path);
4337+
root_path = build_unc_path_to_root(vol, cifs_sb, false);
4338+
if (IS_ERR(root_path)) {
4339+
rc = PTR_ERR(root_path);
4340+
root_path = NULL;
4341+
goto error;
4342+
}
4343+
/* Cache out resolved root server */
4344+
(void)dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
4345+
root_path + 1, NULL, NULL);
4346+
/*
4347+
* Save root tcon for additional DFS requests to update or create a new
4348+
* DFS cache entry, or even perform DFS failover.
4349+
*/
4350+
spin_lock(&cifs_tcp_ses_lock);
4351+
tcon->tc_count++;
4352+
tcon->dfs_path = root_path;
4353+
root_path = NULL;
4354+
tcon->remap = cifs_remap(cifs_sb);
4355+
spin_unlock(&cifs_tcp_ses_lock);
4356+
4357+
root_tcon = tcon;
4358+
41684359
for (count = 1; ;) {
41694360
if (!rc && tcon) {
41704361
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)
41824373
break;
41834374
}
41844375

4376+
kfree(full_path);
4377+
full_path = build_unc_path_to_root(vol, cifs_sb, true);
4378+
if (IS_ERR(full_path)) {
4379+
rc = PTR_ERR(full_path);
4380+
full_path = NULL;
4381+
break;
4382+
}
4383+
41854384
old_mountdata = cifs_sb->mountdata;
4186-
rc = expand_dfs_referral(xid, tcon->ses, vol, cifs_sb,
4385+
rc = expand_dfs_referral(xid, root_tcon->ses, vol, cifs_sb,
41874386
true);
41884387
if (rc)
41894388
break;
@@ -4194,11 +4393,18 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
41944393
&tcon);
41954394
}
41964395
if (rc) {
4396+
if (rc == -EACCES || rc == -EOPNOTSUPP)
4397+
break;
4398+
/* Perform DFS failover to any other DFS targets */
4399+
rc = mount_do_dfs_failover(full_path + 1, cifs_sb, vol,
4400+
root_tcon->ses, &xid,
4401+
&server, &ses, &tcon);
41974402
if (rc == -EACCES || rc == -EOPNOTSUPP || !server ||
41984403
!ses)
41994404
goto error;
42004405
}
42014406
}
4407+
cifs_put_tcon(root_tcon);
42024408

42034409
if (rc)
42044410
goto error;
@@ -4214,6 +4420,8 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
42144420
return mount_setup_tlink(cifs_sb, ses, tcon);
42154421

42164422
error:
4423+
kfree(full_path);
4424+
kfree(root_path);
42174425
mount_put_conns(cifs_sb, xid, server, ses, tcon);
42184426
return rc;
42194427
}

fs/cifs/misc.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ tconInfoFree(struct cifs_tcon *buf_to_free)
146146
kfree(buf_to_free->nativeFileSystem);
147147
kzfree(buf_to_free->password);
148148
kfree(buf_to_free->crfid.fid);
149+
#ifdef CONFIG_CIFS_DFS_UPCALL
150+
kfree(buf_to_free->dfs_path);
151+
#endif
149152
kfree(buf_to_free);
150153
}
151154

0 commit comments

Comments
 (0)