summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaulo Alcantara <palcantara@suse.de>2018-11-14 13:53:52 -0500
committerSteve French <stfrench@microsoft.com>2018-12-28 11:10:29 -0500
commit4a367dc0443566f87d73f2cdb94703b0e1374315 (patch)
tree056bdad61133a901b35093e69987b6abe0c4a6fd
parent5a650501eb8cb785593155441c5b0b9900edfcdf (diff)
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>
-rw-r--r--fs/cifs/cifs_dfs_ref.c20
-rw-r--r--fs/cifs/connect.c228
-rw-r--r--fs/cifs/misc.c3
3 files changed, 236 insertions, 15 deletions
diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c
index 8bd6f6b76da8..d9b99abe1243 100644
--- a/fs/cifs/cifs_dfs_ref.c
+++ b/fs/cifs/cifs_dfs_ref.c
@@ -255,20 +255,30 @@ static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt,
255{ 255{
256 struct vfsmount *mnt; 256 struct vfsmount *mnt;
257 char *mountdata; 257 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, '/');
259 269
260 /* strip first '\' from fullpath */ 270 /* strip first '\' from fullpath */
261 mountdata = cifs_compose_mount_options(cifs_sb->mountdata, 271 mountdata = cifs_compose_mount_options(cifs_sb->mountdata,
262 fullpath + 1, ref, &devname); 272 fullpath + 1, ref, NULL);
263 273 if (IS_ERR(mountdata)) {
264 if (IS_ERR(mountdata)) 274 kfree(devname);
265 return (struct vfsmount *)mountdata; 275 return (struct vfsmount *)mountdata;
276 }
266 277
267 mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata); 278 mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata);
268 kfree(mountdata); 279 kfree(mountdata);
269 kfree(devname); 280 kfree(devname);
270 return mnt; 281 return mnt;
271
272} 282}
273 283
274static void dump_referral(const struct dfs_info3_param *ref) 284static void dump_referral(const struct dfs_info3_param *ref)
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 182b16e56749..658a0d191056 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -3891,10 +3891,11 @@ static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
3891 */ 3891 */
3892static char * 3892static char *
3893build_unc_path_to_root(const struct smb_vol *vol, 3893build_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)
3895{ 3895{
3896 char *full_path, *pos; 3896 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;
3898 unsigned int unc_len = strnlen(vol->UNC, MAX_TREE_SIZE + 1); 3899 unsigned int unc_len = strnlen(vol->UNC, MAX_TREE_SIZE + 1);
3899 3900
3900 full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL); 3901 full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
@@ -3939,7 +3940,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
3939 if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) 3940 if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
3940 return -EREMOTE; 3941 return -EREMOTE;
3941 3942
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);
3943 if (IS_ERR(full_path)) 3944 if (IS_ERR(full_path))
3944 return PTR_ERR(full_path); 3945 return PTR_ERR(full_path);
3945 3946
@@ -3971,6 +3972,143 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
3971 kfree(full_path); 3972 kfree(full_path);
3972 return rc; 3973 return rc;
3973} 3974}
3975
3976static 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
3987static 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
4012static 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
4068static 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}
3974#endif 4112#endif
3975 4113
3976static int 4114static int
@@ -4123,22 +4261,47 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
4123 int rc = 0; 4261 int rc = 0;
4124 unsigned int xid; 4262 unsigned int xid;
4125 struct cifs_ses *ses; 4263 struct cifs_ses *ses;
4264 struct cifs_tcon *root_tcon = NULL;
4126 struct cifs_tcon *tcon = NULL; 4265 struct cifs_tcon *tcon = NULL;
4127 struct TCP_Server_Info *server; 4266 struct TCP_Server_Info *server;
4267 char *root_path = NULL, *full_path = NULL;
4128 char *old_mountdata; 4268 char *old_mountdata;
4129 int count; 4269 int count;
4130 4270
4131 rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); 4271 rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
4132 if (!rc && tcon) { 4272 if (!rc && tcon) {
4133 rc = is_path_remote(cifs_sb, vol, xid, server, tcon); 4273 /* If not a standalone DFS root, then check if path is remote */
4134 if (!rc) 4274 rc = dfs_cache_find(xid, ses, cifs_sb->local_nls,
4135 goto out; 4275 cifs_remap(cifs_sb), vol->UNC + 1, NULL,
4136 if (rc != -EREMOTE) 4276 NULL);
4137 goto error; 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 }
4138 } 4284 }
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)
4140 goto error; 4297 goto error;
4141 4298
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 }
4142 4305
4143 /* 4306 /*
4144 * Perform an unconditional check for whether there are DFS 4307 * 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)
4163 if (rc) { 4326 if (rc) {
4164 if (rc == -EACCES || rc == -EOPNOTSUPP) 4327 if (rc == -EACCES || rc == -EOPNOTSUPP)
4165 goto error; 4328 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;
4166 } 4334 }
4167 4335
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
4168 for (count = 1; ;) { 4359 for (count = 1; ;) {
4169 if (!rc && tcon) { 4360 if (!rc && tcon) {
4170 rc = is_path_remote(cifs_sb, vol, xid, server, tcon); 4361 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)
4182 break; 4373 break;
4183 } 4374 }
4184 4375
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
4185 old_mountdata = cifs_sb->mountdata; 4384 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,
4187 true); 4386 true);
4188 if (rc) 4387 if (rc)
4189 break; 4388 break;
@@ -4194,11 +4393,18 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
4194 &tcon); 4393 &tcon);
4195 } 4394 }
4196 if (rc) { 4395 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);
4197 if (rc == -EACCES || rc == -EOPNOTSUPP || !server || 4402 if (rc == -EACCES || rc == -EOPNOTSUPP || !server ||
4198 !ses) 4403 !ses)
4199 goto error; 4404 goto error;
4200 } 4405 }
4201 } 4406 }
4407 cifs_put_tcon(root_tcon);
4202 4408
4203 if (rc) 4409 if (rc)
4204 goto error; 4410 goto error;
@@ -4214,6 +4420,8 @@ out:
4214 return mount_setup_tlink(cifs_sb, ses, tcon); 4420 return mount_setup_tlink(cifs_sb, ses, tcon);
4215 4421
4216error: 4422error:
4423 kfree(full_path);
4424 kfree(root_path);
4217 mount_put_conns(cifs_sb, xid, server, ses, tcon); 4425 mount_put_conns(cifs_sb, xid, server, ses, tcon);
4218 return rc; 4426 return rc;
4219} 4427}
diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
index 10ae1a35b6f7..7c858d4c66f3 100644
--- a/fs/cifs/misc.c
+++ b/fs/cifs/misc.c
@@ -146,6 +146,9 @@ tconInfoFree(struct cifs_tcon *buf_to_free)
146 kfree(buf_to_free->nativeFileSystem); 146 kfree(buf_to_free->nativeFileSystem);
147 kzfree(buf_to_free->password); 147 kzfree(buf_to_free->password);
148 kfree(buf_to_free->crfid.fid); 148 kfree(buf_to_free->crfid.fid);
149#ifdef CONFIG_CIFS_DFS_UPCALL
150 kfree(buf_to_free->dfs_path);
151#endif
149 kfree(buf_to_free); 152 kfree(buf_to_free);
150} 153}
151 154