xref: /openbmc/linux/fs/smb/client/dfs.c (revision 3db55767)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
4  */
5 
6 #include <linux/namei.h>
7 #include "cifsproto.h"
8 #include "cifs_debug.h"
9 #include "dns_resolve.h"
10 #include "fs_context.h"
11 #include "dfs.h"
12 
13 /**
14  * dfs_parse_target_referral - set fs context for dfs target referral
15  *
16  * @full_path: full path in UNC format.
17  * @ref: dfs referral pointer.
18  * @ctx: smb3 fs context pointer.
19  *
20  * Return zero if dfs referral was parsed correctly, otherwise non-zero.
21  */
22 int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
23 			      struct smb3_fs_context *ctx)
24 {
25 	int rc;
26 	const char *prepath = NULL;
27 	char *path;
28 
29 	if (!full_path || !*full_path || !ref || !ctx)
30 		return -EINVAL;
31 
32 	if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
33 		return -EINVAL;
34 
35 	if (strlen(full_path) - ref->path_consumed) {
36 		prepath = full_path + ref->path_consumed;
37 		/* skip initial delimiter */
38 		if (*prepath == '/' || *prepath == '\\')
39 			prepath++;
40 	}
41 
42 	path = cifs_build_devname(ref->node_name, prepath);
43 	if (IS_ERR(path))
44 		return PTR_ERR(path);
45 
46 	rc = smb3_parse_devname(path, ctx);
47 	if (rc)
48 		goto out;
49 
50 	rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);
51 
52 out:
53 	kfree(path);
54 	return rc;
55 }
56 
57 /*
58  * cifs_build_path_to_root returns full path to root when we do not have an
59  * existing connection (tcon)
60  */
61 static char *build_unc_path_to_root(const struct smb3_fs_context *ctx,
62 				    const struct cifs_sb_info *cifs_sb, bool useppath)
63 {
64 	char *full_path, *pos;
65 	unsigned int pplen = useppath && ctx->prepath ? strlen(ctx->prepath) + 1 : 0;
66 	unsigned int unc_len = strnlen(ctx->UNC, MAX_TREE_SIZE + 1);
67 
68 	if (unc_len > MAX_TREE_SIZE)
69 		return ERR_PTR(-EINVAL);
70 
71 	full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
72 	if (full_path == NULL)
73 		return ERR_PTR(-ENOMEM);
74 
75 	memcpy(full_path, ctx->UNC, unc_len);
76 	pos = full_path + unc_len;
77 
78 	if (pplen) {
79 		*pos = CIFS_DIR_SEP(cifs_sb);
80 		memcpy(pos + 1, ctx->prepath, pplen);
81 		pos += pplen;
82 	}
83 
84 	*pos = '\0'; /* add trailing null */
85 	convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb));
86 	cifs_dbg(FYI, "%s: full_path=%s\n", __func__, full_path);
87 	return full_path;
88 }
89 
90 static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
91 {
92 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
93 	int rc;
94 
95 	ctx->leaf_fullpath = (char *)full_path;
96 	rc = cifs_mount_get_session(mnt_ctx);
97 	ctx->leaf_fullpath = NULL;
98 
99 	return rc;
100 }
101 
102 static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
103 {
104 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
105 	struct dfs_root_ses *root_ses;
106 	struct cifs_ses *ses = mnt_ctx->ses;
107 
108 	if (ses) {
109 		root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
110 		if (!root_ses)
111 			return -ENOMEM;
112 
113 		INIT_LIST_HEAD(&root_ses->list);
114 
115 		spin_lock(&cifs_tcp_ses_lock);
116 		ses->ses_count++;
117 		spin_unlock(&cifs_tcp_ses_lock);
118 		root_ses->ses = ses;
119 		list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
120 	}
121 	ctx->dfs_root_ses = ses;
122 	return 0;
123 }
124 
125 static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path,
126 			const struct dfs_cache_tgt_iterator *tit)
127 {
128 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
129 	struct dfs_info3_param ref = {};
130 	bool is_refsrv;
131 	int rc, rc2;
132 
133 	rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
134 	if (rc)
135 		return rc;
136 
137 	rc = dfs_parse_target_referral(full_path + 1, &ref, ctx);
138 	if (rc)
139 		goto out;
140 
141 	cifs_mount_put_conns(mnt_ctx);
142 	rc = get_session(mnt_ctx, ref_path);
143 	if (rc)
144 		goto out;
145 
146 	is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER);
147 
148 	rc = -EREMOTE;
149 	if (ref.flags & DFSREF_STORAGE_SERVER) {
150 		rc = cifs_mount_get_tcon(mnt_ctx);
151 		if (rc)
152 			goto out;
153 
154 		/* some servers may not advertise referral capability under ref.flags */
155 		is_refsrv |= is_tcon_dfs(mnt_ctx->tcon);
156 
157 		rc = cifs_is_path_remote(mnt_ctx);
158 	}
159 
160 	dfs_cache_noreq_update_tgthint(ref_path + 1, tit);
161 
162 	if (rc == -EREMOTE && is_refsrv) {
163 		rc2 = add_root_smb_session(mnt_ctx);
164 		if (rc2)
165 			rc = rc2;
166 	}
167 
168 out:
169 	free_dfs_info_param(&ref);
170 	return rc;
171 }
172 
173 static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
174 {
175 	struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
176 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
177 	char *ref_path = NULL, *full_path = NULL;
178 	struct dfs_cache_tgt_iterator *tit;
179 	struct TCP_Server_Info *server;
180 	struct cifs_tcon *tcon;
181 	char *origin_fullpath = NULL;
182 	int num_links = 0;
183 	int rc;
184 
185 	ref_path = dfs_get_path(cifs_sb, ctx->UNC);
186 	if (IS_ERR(ref_path))
187 		return PTR_ERR(ref_path);
188 
189 	full_path = build_unc_path_to_root(ctx, cifs_sb, true);
190 	if (IS_ERR(full_path)) {
191 		rc = PTR_ERR(full_path);
192 		full_path = NULL;
193 		goto out;
194 	}
195 
196 	origin_fullpath = kstrdup(full_path, GFP_KERNEL);
197 	if (!origin_fullpath) {
198 		rc = -ENOMEM;
199 		goto out;
200 	}
201 
202 	do {
203 		struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
204 
205 		rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl);
206 		if (rc)
207 			break;
208 
209 		tit = dfs_cache_get_tgt_iterator(&tl);
210 		if (!tit) {
211 			cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__,
212 				 ref_path + 1);
213 			rc = -ENOENT;
214 			dfs_cache_free_tgts(&tl);
215 			break;
216 		}
217 
218 		do {
219 			rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit);
220 			if (!rc)
221 				break;
222 			if (rc == -EREMOTE) {
223 				if (++num_links > MAX_NESTED_LINKS) {
224 					rc = -ELOOP;
225 					break;
226 				}
227 				kfree(ref_path);
228 				kfree(full_path);
229 				ref_path = full_path = NULL;
230 
231 				full_path = build_unc_path_to_root(ctx, cifs_sb, true);
232 				if (IS_ERR(full_path)) {
233 					rc = PTR_ERR(full_path);
234 					full_path = NULL;
235 				} else {
236 					ref_path = dfs_get_path(cifs_sb, full_path);
237 					if (IS_ERR(ref_path)) {
238 						rc = PTR_ERR(ref_path);
239 						ref_path = NULL;
240 					}
241 				}
242 				break;
243 			}
244 		} while ((tit = dfs_cache_get_next_tgt(&tl, tit)));
245 		dfs_cache_free_tgts(&tl);
246 	} while (rc == -EREMOTE);
247 
248 	if (!rc) {
249 		server = mnt_ctx->server;
250 		tcon = mnt_ctx->tcon;
251 
252 		mutex_lock(&server->refpath_lock);
253 		spin_lock(&server->srv_lock);
254 		if (!server->origin_fullpath) {
255 			server->origin_fullpath = origin_fullpath;
256 			origin_fullpath = NULL;
257 		}
258 		spin_unlock(&server->srv_lock);
259 		mutex_unlock(&server->refpath_lock);
260 
261 		if (list_empty(&tcon->dfs_ses_list)) {
262 			list_replace_init(&mnt_ctx->dfs_ses_list,
263 					  &tcon->dfs_ses_list);
264 			queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
265 					   dfs_cache_get_ttl() * HZ);
266 		} else {
267 			dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
268 		}
269 	}
270 
271 out:
272 	kfree(origin_fullpath);
273 	kfree(ref_path);
274 	kfree(full_path);
275 	return rc;
276 }
277 
278 int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
279 {
280 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
281 	struct cifs_ses *ses;
282 	char *source = ctx->source;
283 	bool nodfs = ctx->nodfs;
284 	int rc;
285 
286 	*isdfs = false;
287 	/* Temporarily set @ctx->source to NULL as we're not matching DFS
288 	 * superblocks yet.  See cifs_match_super() and match_server().
289 	 */
290 	ctx->source = NULL;
291 	rc = get_session(mnt_ctx, NULL);
292 	if (rc)
293 		goto out;
294 
295 	ctx->dfs_root_ses = mnt_ctx->ses;
296 	/*
297 	 * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
298 	 * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
299 	 *
300 	 * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
301 	 * to respond with PATH_NOT_COVERED to requests that include the prefix.
302 	 */
303 	if (!nodfs) {
304 		rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL);
305 		if (rc) {
306 			if (rc != -ENOENT && rc != -EOPNOTSUPP && rc != -EIO)
307 				goto out;
308 			nodfs = true;
309 		}
310 	}
311 	if (nodfs) {
312 		rc = cifs_mount_get_tcon(mnt_ctx);
313 		if (!rc)
314 			rc = cifs_is_path_remote(mnt_ctx);
315 		goto out;
316 	}
317 
318 	*isdfs = true;
319 	/*
320 	 * Prevent DFS root session of being put in the first call to
321 	 * cifs_mount_put_conns().  If another DFS root server was not found
322 	 * while chasing the referrals (@ctx->dfs_root_ses == @ses), then we
323 	 * can safely put extra refcount of @ses.
324 	 */
325 	ses = mnt_ctx->ses;
326 	mnt_ctx->ses = NULL;
327 	mnt_ctx->server = NULL;
328 	rc = __dfs_mount_share(mnt_ctx);
329 	if (ses == ctx->dfs_root_ses)
330 		cifs_put_smb_ses(ses);
331 out:
332 	/*
333 	 * Restore previous value of @ctx->source so DFS superblock can be
334 	 * matched in cifs_match_super().
335 	 */
336 	ctx->source = source;
337 	return rc;
338 }
339 
340 /* Update dfs referral path of superblock */
341 static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,
342 				  const char *target)
343 {
344 	int rc = 0;
345 	size_t len = strlen(target);
346 	char *refpath, *npath;
347 
348 	if (unlikely(len < 2 || *target != '\\'))
349 		return -EINVAL;
350 
351 	if (target[1] == '\\') {
352 		len += 1;
353 		refpath = kmalloc(len, GFP_KERNEL);
354 		if (!refpath)
355 			return -ENOMEM;
356 
357 		scnprintf(refpath, len, "%s", target);
358 	} else {
359 		len += sizeof("\\");
360 		refpath = kmalloc(len, GFP_KERNEL);
361 		if (!refpath)
362 			return -ENOMEM;
363 
364 		scnprintf(refpath, len, "\\%s", target);
365 	}
366 
367 	npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));
368 	kfree(refpath);
369 
370 	if (IS_ERR(npath)) {
371 		rc = PTR_ERR(npath);
372 	} else {
373 		mutex_lock(&server->refpath_lock);
374 		spin_lock(&server->srv_lock);
375 		kfree(server->leaf_fullpath);
376 		server->leaf_fullpath = npath;
377 		spin_unlock(&server->srv_lock);
378 		mutex_unlock(&server->refpath_lock);
379 	}
380 	return rc;
381 }
382 
383 static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
384 				       bool *target_match)
385 {
386 	int rc = 0;
387 	const char *dfs_host;
388 	size_t dfs_host_len;
389 
390 	*target_match = true;
391 	extract_unc_hostname(share, &dfs_host, &dfs_host_len);
392 
393 	/* Check if hostnames or addresses match */
394 	cifs_server_lock(server);
395 	if (dfs_host_len != strlen(server->hostname) ||
396 	    strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
397 		cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
398 			 (int)dfs_host_len, dfs_host, server->hostname);
399 		rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
400 		if (rc)
401 			cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
402 	}
403 	cifs_server_unlock(server);
404 	return rc;
405 }
406 
407 static void __tree_connect_ipc(const unsigned int xid, char *tree,
408 			       struct cifs_sb_info *cifs_sb,
409 			       struct cifs_ses *ses)
410 {
411 	struct TCP_Server_Info *server = ses->server;
412 	struct cifs_tcon *tcon = ses->tcon_ipc;
413 	int rc;
414 
415 	spin_lock(&ses->ses_lock);
416 	spin_lock(&ses->chan_lock);
417 	if (cifs_chan_needs_reconnect(ses, server) ||
418 	    ses->ses_status != SES_GOOD) {
419 		spin_unlock(&ses->chan_lock);
420 		spin_unlock(&ses->ses_lock);
421 		cifs_server_dbg(FYI, "%s: skipping ipc reconnect due to disconnected ses\n",
422 				__func__);
423 		return;
424 	}
425 	spin_unlock(&ses->chan_lock);
426 	spin_unlock(&ses->ses_lock);
427 
428 	cifs_server_lock(server);
429 	scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
430 	cifs_server_unlock(server);
431 
432 	rc = server->ops->tree_connect(xid, ses, tree, tcon,
433 				       cifs_sb->local_nls);
434 	cifs_server_dbg(FYI, "%s: tree_reconnect %s: %d\n", __func__, tree, rc);
435 	spin_lock(&tcon->tc_lock);
436 	if (rc) {
437 		tcon->status = TID_NEED_TCON;
438 	} else {
439 		tcon->status = TID_GOOD;
440 		tcon->need_reconnect = false;
441 	}
442 	spin_unlock(&tcon->tc_lock);
443 }
444 
445 static void tree_connect_ipc(const unsigned int xid, char *tree,
446 			     struct cifs_sb_info *cifs_sb,
447 			     struct cifs_tcon *tcon)
448 {
449 	struct cifs_ses *ses = tcon->ses;
450 
451 	__tree_connect_ipc(xid, tree, cifs_sb, ses);
452 	__tree_connect_ipc(xid, tree, cifs_sb, CIFS_DFS_ROOT_SES(ses));
453 }
454 
455 static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
456 				     struct cifs_sb_info *cifs_sb, char *tree, bool islink,
457 				     struct dfs_cache_tgt_list *tl)
458 {
459 	int rc;
460 	struct TCP_Server_Info *server = tcon->ses->server;
461 	const struct smb_version_operations *ops = server->ops;
462 	struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
463 	char *share = NULL, *prefix = NULL;
464 	struct dfs_cache_tgt_iterator *tit;
465 	bool target_match;
466 
467 	tit = dfs_cache_get_tgt_iterator(tl);
468 	if (!tit) {
469 		rc = -ENOENT;
470 		goto out;
471 	}
472 
473 	/* Try to tree connect to all dfs targets */
474 	for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
475 		const char *target = dfs_cache_get_tgt_name(tit);
476 		struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl);
477 
478 		kfree(share);
479 		kfree(prefix);
480 		share = prefix = NULL;
481 
482 		/* Check if share matches with tcp ses */
483 		rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
484 		if (rc) {
485 			cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
486 			break;
487 		}
488 
489 		rc = target_share_matches_server(server, share, &target_match);
490 		if (rc)
491 			break;
492 		if (!target_match) {
493 			rc = -EHOSTUNREACH;
494 			continue;
495 		}
496 
497 		dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
498 		tree_connect_ipc(xid, tree, cifs_sb, tcon);
499 
500 		scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
501 		if (!islink) {
502 			rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
503 			break;
504 		}
505 
506 		/*
507 		 * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
508 		 * to it.  Otherwise, cache the dfs referral and then mark current tcp ses for
509 		 * reconnect so either the demultiplex thread or the echo worker will reconnect to
510 		 * newly resolved target.
511 		 */
512 		if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
513 				   NULL, &ntl)) {
514 			rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
515 			if (rc)
516 				continue;
517 
518 			rc = cifs_update_super_prepath(cifs_sb, prefix);
519 		} else {
520 			/* Target is another dfs share */
521 			rc = update_server_fullpath(server, cifs_sb, target);
522 			dfs_cache_free_tgts(tl);
523 
524 			if (!rc) {
525 				rc = -EREMOTE;
526 				list_replace_init(&ntl.tl_list, &tl->tl_list);
527 			} else
528 				dfs_cache_free_tgts(&ntl);
529 		}
530 		break;
531 	}
532 
533 out:
534 	kfree(share);
535 	kfree(prefix);
536 
537 	return rc;
538 }
539 
540 static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
541 				   struct cifs_sb_info *cifs_sb, char *tree, bool islink,
542 				   struct dfs_cache_tgt_list *tl)
543 {
544 	int rc;
545 	int num_links = 0;
546 	struct TCP_Server_Info *server = tcon->ses->server;
547 	char *old_fullpath = server->leaf_fullpath;
548 
549 	do {
550 		rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);
551 		if (!rc || rc != -EREMOTE)
552 			break;
553 	} while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
554 	/*
555 	 * If we couldn't tree connect to any targets from last referral path, then
556 	 * retry it from newly resolved dfs referral.
557 	 */
558 	if (rc && server->leaf_fullpath != old_fullpath)
559 		cifs_signal_cifsd_for_reconnect(server, true);
560 
561 	dfs_cache_free_tgts(tl);
562 	return rc;
563 }
564 
565 int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
566 {
567 	int rc;
568 	struct TCP_Server_Info *server = tcon->ses->server;
569 	const struct smb_version_operations *ops = server->ops;
570 	struct super_block *sb = NULL;
571 	struct cifs_sb_info *cifs_sb;
572 	struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
573 	char *tree;
574 	struct dfs_info3_param ref = {0};
575 
576 	/* only send once per connect */
577 	spin_lock(&tcon->tc_lock);
578 	if (tcon->status != TID_NEW &&
579 	    tcon->status != TID_NEED_TCON) {
580 		spin_unlock(&tcon->tc_lock);
581 		return -EHOSTDOWN;
582 	}
583 
584 	if (tcon->status == TID_GOOD) {
585 		spin_unlock(&tcon->tc_lock);
586 		return 0;
587 	}
588 	tcon->status = TID_IN_TCON;
589 	spin_unlock(&tcon->tc_lock);
590 
591 	tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
592 	if (!tree) {
593 		rc = -ENOMEM;
594 		goto out;
595 	}
596 
597 	if (tcon->ipc) {
598 		cifs_server_lock(server);
599 		scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
600 		cifs_server_unlock(server);
601 		rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
602 		goto out;
603 	}
604 
605 	sb = cifs_get_tcp_super(server);
606 	if (IS_ERR(sb)) {
607 		rc = PTR_ERR(sb);
608 		cifs_dbg(VFS, "%s: could not find superblock: %d\n", __func__, rc);
609 		goto out;
610 	}
611 
612 	cifs_sb = CIFS_SB(sb);
613 
614 	/* If it is not dfs or there was no cached dfs referral, then reconnect to same share */
615 	if (!server->leaf_fullpath ||
616 	    dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
617 		rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, cifs_sb->local_nls);
618 		goto out;
619 	}
620 
621 	rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
622 				     &tl);
623 	free_dfs_info_param(&ref);
624 
625 out:
626 	kfree(tree);
627 	cifs_put_tcp_super(sb);
628 
629 	if (rc) {
630 		spin_lock(&tcon->tc_lock);
631 		if (tcon->status == TID_IN_TCON)
632 			tcon->status = TID_NEED_TCON;
633 		spin_unlock(&tcon->tc_lock);
634 	} else {
635 		spin_lock(&tcon->tc_lock);
636 		if (tcon->status == TID_IN_TCON)
637 			tcon->status = TID_GOOD;
638 		spin_unlock(&tcon->tc_lock);
639 		tcon->need_reconnect = false;
640 	}
641 
642 	return rc;
643 }
644