.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * Contains the CIFS DFS referral mounting routines used for handling |
---|
3 | 4 | * traversal via DFS junction point |
---|
.. | .. |
---|
6 | 7 | * Copyright (C) International Business Machines Corp., 2008 |
---|
7 | 8 | * Author(s): Igor Mammedov (niallain@gmail.com) |
---|
8 | 9 | * Steve French (sfrench@us.ibm.com) |
---|
9 | | - * This program is free software; you can redistribute it and/or |
---|
10 | | - * modify it under the terms of the GNU General Public License |
---|
11 | | - * as published by the Free Software Foundation; either version |
---|
12 | | - * 2 of the License, or (at your option) any later version. |
---|
13 | 10 | */ |
---|
14 | 11 | |
---|
15 | 12 | #include <linux/dcache.h> |
---|
.. | .. |
---|
25 | 22 | #include "dns_resolve.h" |
---|
26 | 23 | #include "cifs_debug.h" |
---|
27 | 24 | #include "cifs_unicode.h" |
---|
| 25 | +#include "dfs_cache.h" |
---|
28 | 26 | |
---|
29 | 27 | static LIST_HEAD(cifs_dfs_automount_list); |
---|
30 | 28 | |
---|
.. | .. |
---|
122 | 120 | |
---|
123 | 121 | |
---|
124 | 122 | /** |
---|
125 | | - * cifs_compose_mount_options - creates mount options for refferral |
---|
| 123 | + * cifs_compose_mount_options - creates mount options for referral |
---|
126 | 124 | * @sb_mountdata: parent/root DFS mount options (template) |
---|
127 | 125 | * @fullpath: full path in UNC format |
---|
128 | | - * @ref: server's referral |
---|
129 | | - * @devname: pointer for saving device name |
---|
| 126 | + * @ref: optional server's referral |
---|
| 127 | + * @devname: optional pointer for saving device name |
---|
130 | 128 | * |
---|
131 | 129 | * creates mount options for submount based on template options sb_mountdata |
---|
132 | 130 | * and replacing unc,ip,prefixpath options with ones we've got form ref_unc. |
---|
133 | 131 | * |
---|
134 | 132 | * Returns: pointer to new mount options or ERR_PTR. |
---|
135 | | - * Caller is responcible for freeing retunrned value if it is not error. |
---|
| 133 | + * Caller is responsible for freeing returned value if it is not error. |
---|
136 | 134 | */ |
---|
137 | 135 | char *cifs_compose_mount_options(const char *sb_mountdata, |
---|
138 | 136 | const char *fullpath, |
---|
.. | .. |
---|
140 | 138 | char **devname) |
---|
141 | 139 | { |
---|
142 | 140 | int rc; |
---|
| 141 | + char *name; |
---|
143 | 142 | char *mountdata = NULL; |
---|
144 | 143 | const char *prepath = NULL; |
---|
145 | 144 | int md_len; |
---|
.. | .. |
---|
151 | 150 | if (sb_mountdata == NULL) |
---|
152 | 151 | return ERR_PTR(-EINVAL); |
---|
153 | 152 | |
---|
154 | | - if (strlen(fullpath) - ref->path_consumed) { |
---|
155 | | - prepath = fullpath + ref->path_consumed; |
---|
156 | | - /* skip initial delimiter */ |
---|
157 | | - if (*prepath == '/' || *prepath == '\\') |
---|
158 | | - prepath++; |
---|
| 153 | + if (ref) { |
---|
| 154 | + if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0)) |
---|
| 155 | + return ERR_PTR(-EINVAL); |
---|
| 156 | + |
---|
| 157 | + if (strlen(fullpath) - ref->path_consumed) { |
---|
| 158 | + prepath = fullpath + ref->path_consumed; |
---|
| 159 | + /* skip initial delimiter */ |
---|
| 160 | + if (*prepath == '/' || *prepath == '\\') |
---|
| 161 | + prepath++; |
---|
| 162 | + } |
---|
| 163 | + |
---|
| 164 | + name = cifs_build_devname(ref->node_name, prepath); |
---|
| 165 | + if (IS_ERR(name)) { |
---|
| 166 | + rc = PTR_ERR(name); |
---|
| 167 | + name = NULL; |
---|
| 168 | + goto compose_mount_options_err; |
---|
| 169 | + } |
---|
| 170 | + } else { |
---|
| 171 | + name = cifs_build_devname((char *)fullpath, NULL); |
---|
| 172 | + if (IS_ERR(name)) { |
---|
| 173 | + rc = PTR_ERR(name); |
---|
| 174 | + name = NULL; |
---|
| 175 | + goto compose_mount_options_err; |
---|
| 176 | + } |
---|
159 | 177 | } |
---|
160 | 178 | |
---|
161 | | - *devname = cifs_build_devname(ref->node_name, prepath); |
---|
162 | | - if (IS_ERR(*devname)) { |
---|
163 | | - rc = PTR_ERR(*devname); |
---|
164 | | - *devname = NULL; |
---|
165 | | - goto compose_mount_options_err; |
---|
166 | | - } |
---|
167 | | - |
---|
168 | | - rc = dns_resolve_server_name_to_ip(*devname, &srvIP); |
---|
| 179 | + rc = dns_resolve_server_name_to_ip(name, &srvIP); |
---|
169 | 180 | if (rc < 0) { |
---|
170 | 181 | cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n", |
---|
171 | | - __func__, *devname, rc); |
---|
| 182 | + __func__, name, rc); |
---|
172 | 183 | goto compose_mount_options_err; |
---|
173 | 184 | } |
---|
174 | 185 | |
---|
.. | .. |
---|
224 | 235 | strcat(mountdata, "ip="); |
---|
225 | 236 | strcat(mountdata, srvIP); |
---|
226 | 237 | |
---|
| 238 | + if (devname) |
---|
| 239 | + *devname = name; |
---|
| 240 | + else |
---|
| 241 | + kfree(name); |
---|
| 242 | + |
---|
227 | 243 | /*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/ |
---|
228 | 244 | /*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/ |
---|
229 | 245 | |
---|
.. | .. |
---|
234 | 250 | compose_mount_options_err: |
---|
235 | 251 | kfree(mountdata); |
---|
236 | 252 | mountdata = ERR_PTR(rc); |
---|
237 | | - kfree(*devname); |
---|
238 | | - *devname = NULL; |
---|
| 253 | + kfree(name); |
---|
239 | 254 | goto compose_mount_options_out; |
---|
240 | 255 | } |
---|
241 | 256 | |
---|
242 | 257 | /** |
---|
243 | | - * cifs_dfs_do_refmount - mounts specified path using provided refferal |
---|
| 258 | + * cifs_dfs_do_mount - mounts specified path using DFS full path |
---|
| 259 | + * |
---|
| 260 | + * Always pass down @fullpath to smb3_do_mount() so we can use the root server |
---|
| 261 | + * to perform failover in case we failed to connect to the first target in the |
---|
| 262 | + * referral. |
---|
| 263 | + * |
---|
244 | 264 | * @cifs_sb: parent/root superblock |
---|
245 | 265 | * @fullpath: full path in UNC format |
---|
246 | | - * @ref: server's referral |
---|
247 | 266 | */ |
---|
248 | | -static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt, |
---|
249 | | - struct cifs_sb_info *cifs_sb, |
---|
250 | | - const char *fullpath, const struct dfs_info3_param *ref) |
---|
| 267 | +static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt, |
---|
| 268 | + struct cifs_sb_info *cifs_sb, |
---|
| 269 | + const char *fullpath) |
---|
251 | 270 | { |
---|
252 | 271 | struct vfsmount *mnt; |
---|
253 | 272 | char *mountdata; |
---|
254 | | - char *devname = NULL; |
---|
| 273 | + char *devname; |
---|
| 274 | + |
---|
| 275 | + devname = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); |
---|
| 276 | + if (!devname) |
---|
| 277 | + return ERR_PTR(-ENOMEM); |
---|
| 278 | + |
---|
| 279 | + convert_delimiter(devname, '/'); |
---|
255 | 280 | |
---|
256 | 281 | /* strip first '\' from fullpath */ |
---|
257 | 282 | mountdata = cifs_compose_mount_options(cifs_sb->mountdata, |
---|
258 | | - fullpath + 1, ref, &devname); |
---|
259 | | - |
---|
260 | | - if (IS_ERR(mountdata)) |
---|
| 283 | + fullpath + 1, NULL, NULL); |
---|
| 284 | + if (IS_ERR(mountdata)) { |
---|
| 285 | + kfree(devname); |
---|
261 | 286 | return (struct vfsmount *)mountdata; |
---|
| 287 | + } |
---|
262 | 288 | |
---|
263 | 289 | mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata); |
---|
264 | 290 | kfree(mountdata); |
---|
265 | 291 | kfree(devname); |
---|
266 | 292 | return mnt; |
---|
267 | | - |
---|
268 | | -} |
---|
269 | | - |
---|
270 | | -static void dump_referral(const struct dfs_info3_param *ref) |
---|
271 | | -{ |
---|
272 | | - cifs_dbg(FYI, "DFS: ref path: %s\n", ref->path_name); |
---|
273 | | - cifs_dbg(FYI, "DFS: node path: %s\n", ref->node_name); |
---|
274 | | - cifs_dbg(FYI, "DFS: fl: %d, srv_type: %d\n", |
---|
275 | | - ref->flags, ref->server_type); |
---|
276 | | - cifs_dbg(FYI, "DFS: ref_flags: %d, path_consumed: %d\n", |
---|
277 | | - ref->ref_flag, ref->path_consumed); |
---|
278 | 293 | } |
---|
279 | 294 | |
---|
280 | 295 | /* |
---|
.. | .. |
---|
282 | 297 | */ |
---|
283 | 298 | static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) |
---|
284 | 299 | { |
---|
285 | | - struct dfs_info3_param *referrals = NULL; |
---|
286 | | - unsigned int num_referrals = 0; |
---|
287 | 300 | struct cifs_sb_info *cifs_sb; |
---|
288 | 301 | struct cifs_ses *ses; |
---|
289 | | - char *full_path; |
---|
| 302 | + struct cifs_tcon *tcon; |
---|
| 303 | + char *full_path, *root_path; |
---|
290 | 304 | unsigned int xid; |
---|
291 | | - int i; |
---|
292 | 305 | int rc; |
---|
293 | 306 | struct vfsmount *mnt; |
---|
294 | | - struct tcon_link *tlink; |
---|
295 | 307 | |
---|
296 | 308 | cifs_dbg(FYI, "in %s\n", __func__); |
---|
297 | 309 | BUG_ON(IS_ROOT(mntpt)); |
---|
.. | .. |
---|
304 | 316 | */ |
---|
305 | 317 | mnt = ERR_PTR(-ENOMEM); |
---|
306 | 318 | |
---|
| 319 | + cifs_sb = CIFS_SB(mntpt->d_sb); |
---|
| 320 | + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) { |
---|
| 321 | + mnt = ERR_PTR(-EREMOTE); |
---|
| 322 | + goto cdda_exit; |
---|
| 323 | + } |
---|
| 324 | + |
---|
307 | 325 | /* always use tree name prefix */ |
---|
308 | 326 | full_path = build_path_from_dentry_optional_prefix(mntpt, true); |
---|
309 | 327 | if (full_path == NULL) |
---|
310 | 328 | goto cdda_exit; |
---|
311 | 329 | |
---|
312 | | - cifs_sb = CIFS_SB(mntpt->d_sb); |
---|
313 | | - tlink = cifs_sb_tlink(cifs_sb); |
---|
314 | | - if (IS_ERR(tlink)) { |
---|
315 | | - mnt = ERR_CAST(tlink); |
---|
| 330 | + convert_delimiter(full_path, '\\'); |
---|
| 331 | + |
---|
| 332 | + cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); |
---|
| 333 | + |
---|
| 334 | + if (!cifs_sb_master_tlink(cifs_sb)) { |
---|
| 335 | + cifs_dbg(FYI, "%s: master tlink is NULL\n", __func__); |
---|
316 | 336 | goto free_full_path; |
---|
317 | 337 | } |
---|
318 | | - ses = tlink_tcon(tlink)->ses; |
---|
319 | 338 | |
---|
320 | | - xid = get_xid(); |
---|
321 | | - rc = get_dfs_path(xid, ses, full_path + 1, cifs_sb->local_nls, |
---|
322 | | - &num_referrals, &referrals, |
---|
323 | | - cifs_remap(cifs_sb)); |
---|
324 | | - free_xid(xid); |
---|
325 | | - |
---|
326 | | - cifs_put_tlink(tlink); |
---|
327 | | - |
---|
328 | | - mnt = ERR_PTR(-ENOENT); |
---|
329 | | - for (i = 0; i < num_referrals; i++) { |
---|
330 | | - int len; |
---|
331 | | - dump_referral(referrals + i); |
---|
332 | | - /* connect to a node */ |
---|
333 | | - len = strlen(referrals[i].node_name); |
---|
334 | | - if (len < 2) { |
---|
335 | | - cifs_dbg(VFS, "%s: Net Address path too short: %s\n", |
---|
336 | | - __func__, referrals[i].node_name); |
---|
337 | | - mnt = ERR_PTR(-EINVAL); |
---|
338 | | - break; |
---|
339 | | - } |
---|
340 | | - mnt = cifs_dfs_do_refmount(mntpt, cifs_sb, |
---|
341 | | - full_path, referrals + i); |
---|
342 | | - cifs_dbg(FYI, "%s: cifs_dfs_do_refmount:%s , mnt:%p\n", |
---|
343 | | - __func__, referrals[i].node_name, mnt); |
---|
344 | | - if (!IS_ERR(mnt)) |
---|
345 | | - goto success; |
---|
| 339 | + tcon = cifs_sb_master_tcon(cifs_sb); |
---|
| 340 | + if (!tcon) { |
---|
| 341 | + cifs_dbg(FYI, "%s: master tcon is NULL\n", __func__); |
---|
| 342 | + goto free_full_path; |
---|
346 | 343 | } |
---|
347 | 344 | |
---|
348 | | - /* no valid submounts were found; return error from get_dfs_path() by |
---|
349 | | - * preference */ |
---|
350 | | - if (rc != 0) |
---|
351 | | - mnt = ERR_PTR(rc); |
---|
| 345 | + root_path = kstrdup(tcon->treeName, GFP_KERNEL); |
---|
| 346 | + if (!root_path) { |
---|
| 347 | + mnt = ERR_PTR(-ENOMEM); |
---|
| 348 | + goto free_full_path; |
---|
| 349 | + } |
---|
| 350 | + cifs_dbg(FYI, "%s: root path: %s\n", __func__, root_path); |
---|
352 | 351 | |
---|
353 | | -success: |
---|
354 | | - free_dfs_info_array(referrals, num_referrals); |
---|
| 352 | + ses = tcon->ses; |
---|
| 353 | + xid = get_xid(); |
---|
| 354 | + |
---|
| 355 | + /* |
---|
| 356 | + * If DFS root has been expired, then unconditionally fetch it again to |
---|
| 357 | + * refresh DFS referral cache. |
---|
| 358 | + */ |
---|
| 359 | + rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), |
---|
| 360 | + root_path + 1, NULL, NULL); |
---|
| 361 | + if (!rc) { |
---|
| 362 | + rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, |
---|
| 363 | + cifs_remap(cifs_sb), full_path + 1, |
---|
| 364 | + NULL, NULL); |
---|
| 365 | + } |
---|
| 366 | + |
---|
| 367 | + free_xid(xid); |
---|
| 368 | + |
---|
| 369 | + if (rc) { |
---|
| 370 | + mnt = ERR_PTR(rc); |
---|
| 371 | + goto free_root_path; |
---|
| 372 | + } |
---|
| 373 | + /* |
---|
| 374 | + * OK - we were able to get and cache a referral for @full_path. |
---|
| 375 | + * |
---|
| 376 | + * Now, pass it down to cifs_mount() and it will retry every available |
---|
| 377 | + * node server in case of failures - no need to do it here. |
---|
| 378 | + */ |
---|
| 379 | + mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path); |
---|
| 380 | + cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, |
---|
| 381 | + full_path + 1, mnt); |
---|
| 382 | + |
---|
| 383 | +free_root_path: |
---|
| 384 | + kfree(root_path); |
---|
355 | 385 | free_full_path: |
---|
356 | 386 | kfree(full_path); |
---|
357 | 387 | cdda_exit: |
---|