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