diff options
author | Paulo Alcantara <palcantara@suse.de> | 2018-11-14 13:53:52 -0500 |
---|---|---|
committer | Steve French <stfrench@microsoft.com> | 2018-12-28 11:10:29 -0500 |
commit | 4a367dc0443566f87d73f2cdb94703b0e1374315 (patch) | |
tree | 056bdad61133a901b35093e69987b6abe0c4a6fd | |
parent | 5a650501eb8cb785593155441c5b0b9900edfcdf (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.c | 20 | ||||
-rw-r--r-- | fs/cifs/connect.c | 228 | ||||
-rw-r--r-- | fs/cifs/misc.c | 3 |
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 | ||
274 | static void dump_referral(const struct dfs_info3_param *ref) | 284 | static 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 | */ |
3892 | static char * | 3892 | static char * |
3893 | build_unc_path_to_root(const struct smb_vol *vol, | 3893 | 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) |
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 | |||
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 | } | ||
3974 | #endif | 4112 | #endif |
3975 | 4113 | ||
3976 | static int | 4114 | static 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 | ||
4216 | error: | 4422 | error: |
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 | ||