blob: a93dbca1411b25b941703d06eb795e54b37a37d8 [file] [log] [blame]
Paulo Alcantaraabdb1742022-10-04 18:41:27 -03001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
4 */
5
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -03006#include <linux/namei.h>
Paulo Alcantaraabdb1742022-10-04 18:41:27 -03007#include "cifsproto.h"
8#include "cifs_debug.h"
9#include "dns_resolve.h"
10#include "fs_context.h"
11#include "dfs.h"
12
Paulo Alcantaraabdb1742022-10-04 18:41:27 -030013/**
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 */
22int 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
Paulo Alcantara6d740162022-10-04 18:41:36 -030050 rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);
Paulo Alcantaraabdb1742022-10-04 18:41:27 -030051
52out:
53 kfree(path);
54 return rc;
55}
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -030056
57/*
58 * cifs_build_path_to_root returns full path to root when we do not have an
59 * existing connection (tcon)
60 */
61static 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
90static 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
Paulo Alcantara8e355412023-04-23 23:26:51 -0300102static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300103{
Paulo Alcantarab56bce502023-03-14 20:32:53 -0300104 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
Paulo Alcantara396935d2023-03-14 20:32:54 -0300105 struct dfs_root_ses *root_ses;
Paulo Alcantarab56bce502023-03-14 20:32:53 -0300106 struct cifs_ses *ses = mnt_ctx->ses;
107
108 if (ses) {
Paulo Alcantara396935d2023-03-14 20:32:54 -0300109 root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
110 if (!root_ses)
111 return -ENOMEM;
112
113 INIT_LIST_HEAD(&root_ses->list);
114
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300115 spin_lock(&cifs_tcp_ses_lock);
Paulo Alcantarab56bce502023-03-14 20:32:53 -0300116 ses->ses_count++;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300117 spin_unlock(&cifs_tcp_ses_lock);
Paulo Alcantara396935d2023-03-14 20:32:54 -0300118 root_ses->ses = ses;
119 list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300120 }
Paulo Alcantara396935d2023-03-14 20:32:54 -0300121 ctx->dfs_root_ses = ses;
122 return 0;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300123}
124
125static 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 = {};
Paulo Alcantara8e355412023-04-23 23:26:51 -0300130 bool is_refsrv;
Paulo Alcantara396935d2023-03-14 20:32:54 -0300131 int rc, rc2;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300132
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
Paulo Alcantara396935d2023-03-14 20:32:54 -0300146 is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER);
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300147
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 */
Paulo Alcantara396935d2023-03-14 20:32:54 -0300155 is_refsrv |= is_tcon_dfs(mnt_ctx->tcon);
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300156
157 rc = cifs_is_path_remote(mnt_ctx);
158 }
159
Paulo Alcantara6be2ea32023-04-27 04:40:08 -0300160 dfs_cache_noreq_update_tgthint(ref_path + 1, tit);
161
Paulo Alcantara396935d2023-03-14 20:32:54 -0300162 if (rc == -EREMOTE && is_refsrv) {
Paulo Alcantara8e355412023-04-23 23:26:51 -0300163 rc2 = add_root_smb_session(mnt_ctx);
Paulo Alcantara396935d2023-03-14 20:32:54 -0300164 if (rc2)
165 rc = rc2;
166 }
167
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300168out:
169 free_dfs_info_param(&ref);
170 return rc;
171}
172
173static 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;
Paulo Alcantara396935d2023-03-14 20:32:54 -0300180 struct cifs_tcon *tcon;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300181 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;
Paulo Alcantara396935d2023-03-14 20:32:54 -0300250 tcon = mnt_ctx->tcon;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300251
252 mutex_lock(&server->refpath_lock);
Paulo Alcantara3dc9c432023-04-26 13:43:53 -0300253 spin_lock(&server->srv_lock);
Paulo Alcantara396935d2023-03-14 20:32:54 -0300254 if (!server->origin_fullpath) {
255 server->origin_fullpath = origin_fullpath;
Paulo Alcantara396935d2023-03-14 20:32:54 -0300256 origin_fullpath = NULL;
257 }
Paulo Alcantara3dc9c432023-04-26 13:43:53 -0300258 spin_unlock(&server->srv_lock);
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300259 mutex_unlock(&server->refpath_lock);
Paulo Alcantara396935d2023-03-14 20:32:54 -0300260
261 if (list_empty(&tcon->dfs_ses_list)) {
262 list_replace_init(&mnt_ctx->dfs_ses_list,
263 &tcon->dfs_ses_list);
Paulo Alcantara6be2ea32023-04-27 04:40:08 -0300264 queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
265 dfs_cache_get_ttl() * HZ);
Paulo Alcantara396935d2023-03-14 20:32:54 -0300266 } else {
267 dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
268 }
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300269 }
270
271out:
272 kfree(origin_fullpath);
273 kfree(ref_path);
274 kfree(full_path);
275 return rc;
276}
277
278int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
279{
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300280 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
Paulo Alcantara8e355412023-04-23 23:26:51 -0300281 struct cifs_ses *ses;
282 char *source = ctx->source;
283 bool nodfs = ctx->nodfs;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300284 int rc;
285
286 *isdfs = false;
Paulo Alcantara8e355412023-04-23 23:26:51 -0300287 /* 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;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300291 rc = get_session(mnt_ctx, NULL);
292 if (rc)
Paulo Alcantara8e355412023-04-23 23:26:51 -0300293 goto out;
294
Paulo Alcantarab56bce502023-03-14 20:32:53 -0300295 ctx->dfs_root_ses = mnt_ctx->ses;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300296 /*
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 */
Paulo Alcantara8e355412023-04-23 23:26:51 -0300303 if (!nodfs) {
304 rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL);
305 if (rc) {
306 if (rc != -ENOENT && rc != -EOPNOTSUPP)
307 goto out;
308 nodfs = true;
309 }
310 }
311 if (nodfs) {
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300312 rc = cifs_mount_get_tcon(mnt_ctx);
Paulo Alcantara8e355412023-04-23 23:26:51 -0300313 if (!rc)
314 rc = cifs_is_path_remote(mnt_ctx);
315 goto out;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300316 }
317
318 *isdfs = true;
Paulo Alcantara8e355412023-04-23 23:26:51 -0300319 /*
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);
331out:
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;
Paulo Alcantaraa1c0d002022-11-17 13:23:49 -0300338}
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300339
340/* Update dfs referral path of superblock */
341static 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);
Paulo Alcantara3dc9c432023-04-26 13:43:53 -0300374 spin_lock(&server->srv_lock);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300375 kfree(server->leaf_fullpath);
376 server->leaf_fullpath = npath;
Paulo Alcantara3dc9c432023-04-26 13:43:53 -0300377 spin_unlock(&server->srv_lock);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300378 mutex_unlock(&server->refpath_lock);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300379 }
380 return rc;
381}
382
Paulo Alcantara39a154fc2022-12-29 18:43:46 -0300383static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
384 bool *target_match)
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300385{
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 */
Paulo Alcantara39a154fc2022-12-29 18:43:46 -0300394 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);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300399 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 }
Paulo Alcantara39a154fc2022-12-29 18:43:46 -0300403 cifs_server_unlock(server);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300404 return rc;
405}
406
Paulo Alcantaraee20d7c2023-04-25 02:42:56 -0300407static 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
445static 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
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300455static 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);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300463 char *share = NULL, *prefix = NULL;
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300464 struct dfs_cache_tgt_iterator *tit;
465 bool target_match;
466
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300467 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 */
Paulo Alcantara3dc9c432023-04-26 13:43:53 -0300483 rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300484 if (rc) {
485 cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
486 break;
487 }
488
Paulo Alcantara39a154fc2022-12-29 18:43:46 -0300489 rc = target_share_matches_server(server, share, &target_match);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300490 if (rc)
491 break;
492 if (!target_match) {
493 rc = -EHOSTUNREACH;
494 continue;
495 }
496
Paulo Alcantara3dc9c432023-04-26 13:43:53 -0300497 dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
Paulo Alcantaraee20d7c2023-04-25 02:42:56 -0300498 tree_connect_ipc(xid, tree, cifs_sb, tcon);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300499
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 }
Paulo Alcantaraee20d7c2023-04-25 02:42:56 -0300505
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300506 /*
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
533out:
534 kfree(share);
535 kfree(prefix);
536
537 return rc;
538}
539
540static 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;
Paulo Alcantara6fbdd5ab2022-12-17 00:46:11 -0300547 char *old_fullpath = server->leaf_fullpath;
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300548
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 /*
Paulo Alcantara6fbdd5ab2022-12-17 00:46:11 -0300555 * If we couldn't tree connect to any targets from last referral path, then
556 * retry it from newly resolved dfs referral.
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300557 */
Paulo Alcantara6fbdd5ab2022-12-17 00:46:11 -0300558 if (rc && server->leaf_fullpath != old_fullpath)
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300559 cifs_signal_cifsd_for_reconnect(server, true);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300560
561 dfs_cache_free_tgts(tl);
562 return rc;
563}
564
565int 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);
Shyam Prasad N2f0e4f02023-03-16 10:45:12 +0000578 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) {
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300585 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) {
Paulo Alcantara39a154fc2022-12-29 18:43:46 -0300598 cifs_server_lock(server);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300599 scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
Paulo Alcantara39a154fc2022-12-29 18:43:46 -0300600 cifs_server_unlock(server);
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300601 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 */
Paulo Alcantara3dc9c432023-04-26 13:43:53 -0300615 if (!server->leaf_fullpath ||
616 dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
Paulo Alcantara1d04a6f2022-12-16 21:41:31 -0300617 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
625out:
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}