2 * Contains the CIFS DFS referral mounting routines used for handling
3 * traversal via DFS junction point
5 * Copyright (c) 2007 Igor Mammedov
6 * Copyright (C) International Business Machines Corp., 2008
7 * Author(s): Igor Mammedov (niallain@gmail.com)
8 * 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.
15 #include <linux/dcache.h>
16 #include <linux/mount.h>
17 #include <linux/namei.h>
18 #include <linux/slab.h>
19 #include <linux/vfs.h>
21 #include <linux/inet.h>
23 #include "cifsproto.h"
25 #include "dns_resolve.h"
26 #include "cifs_debug.h"
27 #include "cifs_unicode.h"
28 #include "dfs_cache.h"
30 static LIST_HEAD(cifs_dfs_automount_list);
32 static void cifs_dfs_expire_automounts(struct work_struct *work);
33 static DECLARE_DELAYED_WORK(cifs_dfs_automount_task,
34 cifs_dfs_expire_automounts);
35 static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ;
37 static void cifs_dfs_expire_automounts(struct work_struct *work)
39 struct list_head *list = &cifs_dfs_automount_list;
41 mark_mounts_for_expiry(list);
42 if (!list_empty(list))
43 schedule_delayed_work(&cifs_dfs_automount_task,
44 cifs_dfs_mountpoint_expiry_timeout);
47 void cifs_dfs_release_automount_timer(void)
49 BUG_ON(!list_empty(&cifs_dfs_automount_list));
50 cancel_delayed_work_sync(&cifs_dfs_automount_task);
54 * cifs_build_devname - build a devicename from a UNC and optional prepath
55 * @nodename: pointer to UNC string
56 * @prepath: pointer to prefixpath (or NULL if there isn't one)
58 * Build a new cifs devicename after chasing a DFS referral. Allocate a buffer
59 * big enough to hold the final thing. Copy the UNC from the nodename, and
60 * concatenate the prepath onto the end of it if there is one.
62 * Returns pointer to the built string, or a ERR_PTR. Caller is responsible
63 * for freeing the returned string.
66 cifs_build_devname(char *nodename, const char *prepath)
73 /* skip over any preceding delimiters */
74 nodename += strspn(nodename, "\\");
76 return ERR_PTR(-EINVAL);
78 /* get length of UNC and set pos to last char */
79 unclen = strlen(nodename);
80 pos = nodename + unclen - 1;
82 /* trim off any trailing delimiters */
83 while (*pos == '\\') {
89 * +2 for preceding "//"
90 * +1 for delimiter between UNC and prepath
91 * +1 for trailing NULL
93 pplen = prepath ? strlen(prepath) : 0;
94 dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL);
96 return ERR_PTR(-ENOMEM);
99 /* add the initial "//" */
105 /* copy in the UNC portion from referral */
106 memcpy(pos, nodename, unclen);
109 /* copy the prefixpath remainder (if there is one) */
113 memcpy(pos, prepath, pplen);
117 /* NULL terminator */
120 convert_delimiter(dev, '/');
126 * cifs_compose_mount_options - creates mount options for refferral
127 * @sb_mountdata: parent/root DFS mount options (template)
128 * @fullpath: full path in UNC format
129 * @ref: server's referral
130 * @devname: optional pointer for saving device name
132 * creates mount options for submount based on template options sb_mountdata
133 * and replacing unc,ip,prefixpath options with ones we've got form ref_unc.
135 * Returns: pointer to new mount options or ERR_PTR.
136 * Caller is responcible for freeing retunrned value if it is not error.
138 char *cifs_compose_mount_options(const char *sb_mountdata,
139 const char *fullpath,
140 const struct dfs_info3_param *ref,
145 char *mountdata = NULL;
146 const char *prepath = NULL;
153 if (sb_mountdata == NULL)
154 return ERR_PTR(-EINVAL);
156 if (strlen(fullpath) - ref->path_consumed) {
157 prepath = fullpath + ref->path_consumed;
158 /* skip initial delimiter */
159 if (*prepath == '/' || *prepath == '\\')
163 name = cifs_build_devname(ref->node_name, prepath);
167 goto compose_mount_options_err;
170 rc = dns_resolve_server_name_to_ip(name, &srvIP);
172 cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n",
174 goto compose_mount_options_err;
178 * In most cases, we'll be building a shorter string than the original,
179 * but we do have to assume that the address in the ip= option may be
180 * much longer than the original. Add the max length of an address
181 * string to the length of the original string to allow for worst case.
183 md_len = strlen(sb_mountdata) + INET6_ADDRSTRLEN;
184 mountdata = kzalloc(md_len + sizeof("ip=") + 1, GFP_KERNEL);
185 if (mountdata == NULL) {
187 goto compose_mount_options_err;
190 /* copy all options except of unc,ip,prefixpath */
192 if (strncmp(sb_mountdata, "sep=", 4) == 0) {
193 sep = sb_mountdata[4];
194 strncpy(mountdata, sb_mountdata, 5);
199 tkn_e = strchr(sb_mountdata + off, sep);
201 noff = strlen(sb_mountdata + off);
203 noff = tkn_e - (sb_mountdata + off) + 1;
205 if (strncasecmp(sb_mountdata + off, "unc=", 4) == 0) {
209 if (strncasecmp(sb_mountdata + off, "ip=", 3) == 0) {
213 if (strncasecmp(sb_mountdata + off, "prefixpath=", 11) == 0) {
217 strncat(mountdata, sb_mountdata + off, noff);
220 strcat(mountdata, sb_mountdata + off);
221 mountdata[md_len] = '\0';
223 /* copy new IP and ref share name */
224 if (mountdata[strlen(mountdata) - 1] != sep)
225 strncat(mountdata, &sep, 1);
226 strcat(mountdata, "ip=");
227 strcat(mountdata, srvIP);
232 /*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/
233 /*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/
235 compose_mount_options_out:
239 compose_mount_options_err:
241 mountdata = ERR_PTR(rc);
243 goto compose_mount_options_out;
247 * cifs_dfs_do_refmount - mounts specified path using provided refferal
248 * @cifs_sb: parent/root superblock
249 * @fullpath: full path in UNC format
250 * @ref: server's referral
252 static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt,
253 struct cifs_sb_info *cifs_sb,
254 const char *fullpath, const struct dfs_info3_param *ref)
256 struct vfsmount *mnt;
261 * Always pass down the DFS full path to smb3_do_mount() so we
262 * can use it later for failover.
264 devname = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL);
266 return ERR_PTR(-ENOMEM);
268 convert_delimiter(devname, '/');
270 /* strip first '\' from fullpath */
271 mountdata = cifs_compose_mount_options(cifs_sb->mountdata,
272 fullpath + 1, ref, NULL);
273 if (IS_ERR(mountdata)) {
275 return (struct vfsmount *)mountdata;
278 mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata);
284 static void dump_referral(const struct dfs_info3_param *ref)
286 cifs_dbg(FYI, "DFS: ref path: %s\n", ref->path_name);
287 cifs_dbg(FYI, "DFS: node path: %s\n", ref->node_name);
288 cifs_dbg(FYI, "DFS: fl: %hd, srv_type: %hd\n",
289 ref->flags, ref->server_type);
290 cifs_dbg(FYI, "DFS: ref_flags: %hd, path_consumed: %hd\n",
291 ref->ref_flag, ref->path_consumed);
295 * Create a vfsmount that we can automount
297 static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
299 struct dfs_info3_param referral = {0};
300 struct cifs_sb_info *cifs_sb;
301 struct cifs_ses *ses;
302 struct cifs_tcon *tcon;
303 char *full_path, *root_path;
307 struct vfsmount *mnt;
309 cifs_dbg(FYI, "in %s\n", __func__);
310 BUG_ON(IS_ROOT(mntpt));
313 * The MSDFS spec states that paths in DFS referral requests and
314 * responses must be prefixed by a single '\' character instead of
315 * the double backslashes usually used in the UNC. This function
316 * gives us the latter, so we must adjust the result.
318 mnt = ERR_PTR(-ENOMEM);
320 cifs_sb = CIFS_SB(mntpt->d_sb);
321 if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) {
322 mnt = ERR_PTR(-EREMOTE);
326 /* always use tree name prefix */
327 full_path = build_path_from_dentry_optional_prefix(mntpt, true);
328 if (full_path == NULL)
331 cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path);
333 if (!cifs_sb_master_tlink(cifs_sb)) {
334 cifs_dbg(FYI, "%s: master tlink is NULL\n", __func__);
338 tcon = cifs_sb_master_tcon(cifs_sb);
340 cifs_dbg(FYI, "%s: master tcon is NULL\n", __func__);
344 root_path = kstrdup(tcon->treeName, GFP_KERNEL);
346 mnt = ERR_PTR(-ENOMEM);
349 cifs_dbg(FYI, "%s: root path: %s\n", __func__, root_path);
355 * If DFS root has been expired, then unconditionally fetch it again to
356 * refresh DFS referral cache.
358 rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
359 root_path + 1, NULL, NULL);
361 rc = dfs_cache_find(xid, ses, cifs_sb->local_nls,
362 cifs_remap(cifs_sb), full_path + 1,
373 dump_referral(&referral);
375 len = strlen(referral.node_name);
377 cifs_dbg(VFS, "%s: Net Address path too short: %s\n",
378 __func__, referral.node_name);
379 mnt = ERR_PTR(-EINVAL);
383 * cifs_mount() will retry every available node server in case
386 mnt = cifs_dfs_do_refmount(mntpt, cifs_sb, full_path, &referral);
387 cifs_dbg(FYI, "%s: cifs_dfs_do_refmount:%s , mnt:%p\n", __func__,
388 referral.node_name, mnt);
391 free_dfs_info_param(&referral);
397 cifs_dbg(FYI, "leaving %s\n" , __func__);
402 * Attempt to automount the referral
404 struct vfsmount *cifs_dfs_d_automount(struct path *path)
406 struct vfsmount *newmnt;
408 cifs_dbg(FYI, "in %s\n", __func__);
410 newmnt = cifs_dfs_do_automount(path->dentry);
411 if (IS_ERR(newmnt)) {
412 cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__);
416 mntget(newmnt); /* prevent immediate expiration */
417 mnt_set_expiry(newmnt, &cifs_dfs_automount_list);
418 schedule_delayed_work(&cifs_dfs_automount_task,
419 cifs_dfs_mountpoint_expiry_timeout);
420 cifs_dbg(FYI, "leaving %s [ok]\n" , __func__);
424 const struct inode_operations cifs_dfs_referral_inode_operations = {