xref: /openbmc/linux/fs/nfs/nfs4namespace.c (revision 2671bfc3)
1f7b422b1SDavid Howells /*
2f7b422b1SDavid Howells  * linux/fs/nfs/nfs4namespace.c
3f7b422b1SDavid Howells  *
4f7b422b1SDavid Howells  * Copyright (C) 2005 Trond Myklebust <Trond.Myklebust@netapp.com>
554ceac45SDavid Howells  * - Modified by David Howells <dhowells@redhat.com>
6f7b422b1SDavid Howells  *
7f7b422b1SDavid Howells  * NFSv4 namespace
8f7b422b1SDavid Howells  */
9f7b422b1SDavid Howells 
10f7b422b1SDavid Howells #include <linux/dcache.h>
11f7b422b1SDavid Howells #include <linux/mount.h>
12f7b422b1SDavid Howells #include <linux/namei.h>
13f7b422b1SDavid Howells #include <linux/nfs_fs.h>
145a0e3ad6STejun Heo #include <linux/slab.h>
15f7b422b1SDavid Howells #include <linux/string.h>
16f7b422b1SDavid Howells #include <linux/sunrpc/clnt.h>
17f7b422b1SDavid Howells #include <linux/vfs.h>
18f7b422b1SDavid Howells #include <linux/inet.h>
19f7b422b1SDavid Howells #include "internal.h"
20c228fd3aSTrond Myklebust #include "nfs4_fs.h"
217d7ea882STrond Myklebust #include "dns_resolve.h"
22f7b422b1SDavid Howells 
23f7b422b1SDavid Howells #define NFSDBG_FACILITY		NFSDBG_VFS
24f7b422b1SDavid Howells 
25f7b422b1SDavid Howells /*
26ef95d31eSTrond Myklebust  * Convert the NFSv4 pathname components into a standard posix path.
27ef95d31eSTrond Myklebust  *
28ef95d31eSTrond Myklebust  * Note that the resulting string will be placed at the end of the buffer
29f7b422b1SDavid Howells  */
30509de811SDavid Howells static inline char *nfs4_pathname_string(const struct nfs4_pathname *pathname,
31f7b422b1SDavid Howells 					 char *buffer, ssize_t buflen)
32f7b422b1SDavid Howells {
33f7b422b1SDavid Howells 	char *end = buffer + buflen;
34f7b422b1SDavid Howells 	int n;
35f7b422b1SDavid Howells 
36f7b422b1SDavid Howells 	*--end = '\0';
37f7b422b1SDavid Howells 	buflen--;
38f7b422b1SDavid Howells 
39f7b422b1SDavid Howells 	n = pathname->ncomponents;
40f7b422b1SDavid Howells 	while (--n >= 0) {
41509de811SDavid Howells 		const struct nfs4_string *component = &pathname->components[n];
42f7b422b1SDavid Howells 		buflen -= component->len + 1;
43f7b422b1SDavid Howells 		if (buflen < 0)
44f7b422b1SDavid Howells 			goto Elong;
45f7b422b1SDavid Howells 		end -= component->len;
46f7b422b1SDavid Howells 		memcpy(end, component->data, component->len);
47f7b422b1SDavid Howells 		*--end = '/';
48f7b422b1SDavid Howells 	}
49f7b422b1SDavid Howells 	return end;
50f7b422b1SDavid Howells Elong:
51f7b422b1SDavid Howells 	return ERR_PTR(-ENAMETOOLONG);
52f7b422b1SDavid Howells }
53f7b422b1SDavid Howells 
5454ceac45SDavid Howells /*
551aba1567SWeston Andros Adamson  * return the path component of "<server>:<path>"
561aba1567SWeston Andros Adamson  *  nfspath - the "<server>:<path>" string
571aba1567SWeston Andros Adamson  *  end - one past the last char that could contain "<server>:"
581aba1567SWeston Andros Adamson  * returns NULL on failure
591aba1567SWeston Andros Adamson  */
601aba1567SWeston Andros Adamson static char *nfs_path_component(const char *nfspath, const char *end)
611aba1567SWeston Andros Adamson {
621aba1567SWeston Andros Adamson 	char *p;
631aba1567SWeston Andros Adamson 
641aba1567SWeston Andros Adamson 	if (*nfspath == '[') {
651aba1567SWeston Andros Adamson 		/* parse [] escaped IPv6 addrs */
661aba1567SWeston Andros Adamson 		p = strchr(nfspath, ']');
671aba1567SWeston Andros Adamson 		if (p != NULL && ++p < end && *p == ':')
681aba1567SWeston Andros Adamson 			return p + 1;
691aba1567SWeston Andros Adamson 	} else {
701aba1567SWeston Andros Adamson 		/* otherwise split on first colon */
711aba1567SWeston Andros Adamson 		p = strchr(nfspath, ':');
721aba1567SWeston Andros Adamson 		if (p != NULL && p < end)
731aba1567SWeston Andros Adamson 			return p + 1;
741aba1567SWeston Andros Adamson 	}
751aba1567SWeston Andros Adamson 	return NULL;
761aba1567SWeston Andros Adamson }
771aba1567SWeston Andros Adamson 
781aba1567SWeston Andros Adamson /*
7954ceac45SDavid Howells  * Determine the mount path as a string
8054ceac45SDavid Howells  */
81b514f872SAl Viro static char *nfs4_path(struct dentry *dentry, char *buffer, ssize_t buflen)
8254ceac45SDavid Howells {
83b514f872SAl Viro 	char *limit;
84b514f872SAl Viro 	char *path = nfs_path(&limit, dentry, buffer, buflen);
85b514f872SAl Viro 	if (!IS_ERR(path)) {
861aba1567SWeston Andros Adamson 		char *path_component = nfs_path_component(path, limit);
871aba1567SWeston Andros Adamson 		if (path_component)
881aba1567SWeston Andros Adamson 			return path_component;
89b514f872SAl Viro 	}
90b514f872SAl Viro 	return path;
9154ceac45SDavid Howells }
9254ceac45SDavid Howells 
9354ceac45SDavid Howells /*
9454ceac45SDavid Howells  * Check that fs_locations::fs_root [RFC3530 6.3] is a prefix for what we
9554ceac45SDavid Howells  * believe to be the server path to this dentry
9654ceac45SDavid Howells  */
97b514f872SAl Viro static int nfs4_validate_fspath(struct dentry *dentry,
9854ceac45SDavid Howells 				const struct nfs4_fs_locations *locations,
9954ceac45SDavid Howells 				char *page, char *page2)
10054ceac45SDavid Howells {
10154ceac45SDavid Howells 	const char *path, *fs_path;
10254ceac45SDavid Howells 
103b514f872SAl Viro 	path = nfs4_path(dentry, page, PAGE_SIZE);
10454ceac45SDavid Howells 	if (IS_ERR(path))
10554ceac45SDavid Howells 		return PTR_ERR(path);
10654ceac45SDavid Howells 
10754ceac45SDavid Howells 	fs_path = nfs4_pathname_string(&locations->fs_path, page2, PAGE_SIZE);
10854ceac45SDavid Howells 	if (IS_ERR(fs_path))
10954ceac45SDavid Howells 		return PTR_ERR(fs_path);
11054ceac45SDavid Howells 
11154ceac45SDavid Howells 	if (strncmp(path, fs_path, strlen(fs_path)) != 0) {
11254ceac45SDavid Howells 		dprintk("%s: path %s does not begin with fsroot %s\n",
1133110ff80SHarvey Harrison 			__func__, path, fs_path);
11454ceac45SDavid Howells 		return -ENOENT;
11554ceac45SDavid Howells 	}
11654ceac45SDavid Howells 
11754ceac45SDavid Howells 	return 0;
11854ceac45SDavid Howells }
11954ceac45SDavid Howells 
1207d7ea882STrond Myklebust static size_t nfs_parse_server_name(char *string, size_t len,
1211b340d01SStanislav Kinsbursky 		struct sockaddr *sa, size_t salen, struct nfs_server *server)
1227d7ea882STrond Myklebust {
1232446ab60STrond Myklebust 	struct net *net = rpc_net_ns(server->client);
1247d7ea882STrond Myklebust 	ssize_t ret;
1257d7ea882STrond Myklebust 
12633faaa38SStanislav Kinsbursky 	ret = rpc_pton(net, string, len, sa, salen);
1277d7ea882STrond Myklebust 	if (ret == 0) {
12833faaa38SStanislav Kinsbursky 		ret = nfs_dns_resolve_name(net, string, len, sa, salen);
1297d7ea882STrond Myklebust 		if (ret < 0)
1307d7ea882STrond Myklebust 			ret = 0;
1317d7ea882STrond Myklebust 	}
1327d7ea882STrond Myklebust 	return ret;
1337d7ea882STrond Myklebust }
1347d7ea882STrond Myklebust 
1352671bfc3SBryan Schumaker rpc_authflavor_t nfs_find_best_sec(struct nfs4_secinfo_flavors *flavors)
1362671bfc3SBryan Schumaker {
1372671bfc3SBryan Schumaker 	struct gss_api_mech *mech;
1382671bfc3SBryan Schumaker 	struct xdr_netobj oid;
1392671bfc3SBryan Schumaker 	int i;
1402671bfc3SBryan Schumaker 	rpc_authflavor_t pseudoflavor = RPC_AUTH_UNIX;
1412671bfc3SBryan Schumaker 
1422671bfc3SBryan Schumaker 	for (i = 0; i < flavors->num_flavors; i++) {
1432671bfc3SBryan Schumaker 		struct nfs4_secinfo_flavor *flavor;
1442671bfc3SBryan Schumaker 		flavor = &flavors->flavors[i];
1452671bfc3SBryan Schumaker 
1462671bfc3SBryan Schumaker 		if (flavor->flavor == RPC_AUTH_NULL || flavor->flavor == RPC_AUTH_UNIX) {
1472671bfc3SBryan Schumaker 			pseudoflavor = flavor->flavor;
1482671bfc3SBryan Schumaker 			break;
1492671bfc3SBryan Schumaker 		} else if (flavor->flavor == RPC_AUTH_GSS) {
1502671bfc3SBryan Schumaker 			oid.len  = flavor->gss.sec_oid4.len;
1512671bfc3SBryan Schumaker 			oid.data = flavor->gss.sec_oid4.data;
1522671bfc3SBryan Schumaker 			mech = gss_mech_get_by_OID(&oid);
1532671bfc3SBryan Schumaker 			if (!mech)
1542671bfc3SBryan Schumaker 				continue;
1552671bfc3SBryan Schumaker 			pseudoflavor = gss_svc_to_pseudoflavor(mech, flavor->gss.service);
1562671bfc3SBryan Schumaker 			gss_mech_put(mech);
1572671bfc3SBryan Schumaker 			break;
1582671bfc3SBryan Schumaker 		}
1592671bfc3SBryan Schumaker 	}
1602671bfc3SBryan Schumaker 
1612671bfc3SBryan Schumaker 	return pseudoflavor;
1622671bfc3SBryan Schumaker }
1632671bfc3SBryan Schumaker 
16472de53ecSBryan Schumaker static rpc_authflavor_t nfs4_negotiate_security(struct inode *inode, struct qstr *name)
16572de53ecSBryan Schumaker {
16672de53ecSBryan Schumaker 	struct page *page;
16772de53ecSBryan Schumaker 	struct nfs4_secinfo_flavors *flavors;
16872de53ecSBryan Schumaker 	rpc_authflavor_t flavor;
16972de53ecSBryan Schumaker 	int err;
17072de53ecSBryan Schumaker 
17172de53ecSBryan Schumaker 	page = alloc_page(GFP_KERNEL);
17272de53ecSBryan Schumaker 	if (!page)
17372de53ecSBryan Schumaker 		return -ENOMEM;
17472de53ecSBryan Schumaker 	flavors = page_address(page);
17572de53ecSBryan Schumaker 
17672de53ecSBryan Schumaker 	err = nfs4_proc_secinfo(inode, name, flavors);
17772de53ecSBryan Schumaker 	if (err < 0) {
17872de53ecSBryan Schumaker 		flavor = err;
17972de53ecSBryan Schumaker 		goto out;
18072de53ecSBryan Schumaker 	}
18172de53ecSBryan Schumaker 
18272de53ecSBryan Schumaker 	flavor = nfs_find_best_sec(flavors);
18372de53ecSBryan Schumaker 
18472de53ecSBryan Schumaker out:
18572de53ecSBryan Schumaker 	put_page(page);
18672de53ecSBryan Schumaker 	return flavor;
18772de53ecSBryan Schumaker }
18872de53ecSBryan Schumaker 
18972de53ecSBryan Schumaker /*
19072de53ecSBryan Schumaker  * Please call rpc_shutdown_client() when you are done with this client.
19172de53ecSBryan Schumaker  */
19272de53ecSBryan Schumaker struct rpc_clnt *nfs4_create_sec_client(struct rpc_clnt *clnt, struct inode *inode,
19372de53ecSBryan Schumaker 					struct qstr *name)
19472de53ecSBryan Schumaker {
19572de53ecSBryan Schumaker 	struct rpc_clnt *clone;
19672de53ecSBryan Schumaker 	struct rpc_auth *auth;
19772de53ecSBryan Schumaker 	rpc_authflavor_t flavor;
19872de53ecSBryan Schumaker 
19972de53ecSBryan Schumaker 	flavor = nfs4_negotiate_security(inode, name);
20072de53ecSBryan Schumaker 	if (flavor < 0)
20172de53ecSBryan Schumaker 		return ERR_PTR(flavor);
20272de53ecSBryan Schumaker 
20372de53ecSBryan Schumaker 	clone = rpc_clone_client(clnt);
20472de53ecSBryan Schumaker 	if (IS_ERR(clone))
20572de53ecSBryan Schumaker 		return clone;
20672de53ecSBryan Schumaker 
20772de53ecSBryan Schumaker 	auth = rpcauth_create(flavor, clone);
20872de53ecSBryan Schumaker 	if (!auth) {
20972de53ecSBryan Schumaker 		rpc_shutdown_client(clone);
21072de53ecSBryan Schumaker 		clone = ERR_PTR(-EIO);
21172de53ecSBryan Schumaker 	}
21272de53ecSBryan Schumaker 
21372de53ecSBryan Schumaker 	return clone;
21472de53ecSBryan Schumaker }
21572de53ecSBryan Schumaker 
2164ada29d5SJ. Bruce Fields static struct vfsmount *try_location(struct nfs_clone_mount *mountdata,
2174ada29d5SJ. Bruce Fields 				     char *page, char *page2,
2184ada29d5SJ. Bruce Fields 				     const struct nfs4_fs_location *location)
2194ada29d5SJ. Bruce Fields {
220364d015eSTrond Myklebust 	const size_t addr_bufsize = sizeof(struct sockaddr_storage);
2214ada29d5SJ. Bruce Fields 	struct vfsmount *mnt = ERR_PTR(-ENOENT);
2224ada29d5SJ. Bruce Fields 	char *mnt_path;
223ef95d31eSTrond Myklebust 	unsigned int maxbuflen;
224460cdbc8SJ. Bruce Fields 	unsigned int s;
2254ada29d5SJ. Bruce Fields 
2264ada29d5SJ. Bruce Fields 	mnt_path = nfs4_pathname_string(&location->rootpath, page2, PAGE_SIZE);
2274ada29d5SJ. Bruce Fields 	if (IS_ERR(mnt_path))
228517be09dSTrond Myklebust 		return ERR_CAST(mnt_path);
2294ada29d5SJ. Bruce Fields 	mountdata->mnt_path = mnt_path;
230ef95d31eSTrond Myklebust 	maxbuflen = mnt_path - 1 - page2;
2314ada29d5SJ. Bruce Fields 
232364d015eSTrond Myklebust 	mountdata->addr = kmalloc(addr_bufsize, GFP_KERNEL);
233364d015eSTrond Myklebust 	if (mountdata->addr == NULL)
234364d015eSTrond Myklebust 		return ERR_PTR(-ENOMEM);
235364d015eSTrond Myklebust 
236460cdbc8SJ. Bruce Fields 	for (s = 0; s < location->nservers; s++) {
237ea31a443SJ. Bruce Fields 		const struct nfs4_string *buf = &location->servers[s];
2384ada29d5SJ. Bruce Fields 
239ef95d31eSTrond Myklebust 		if (buf->len <= 0 || buf->len >= maxbuflen)
2404ada29d5SJ. Bruce Fields 			continue;
2414ada29d5SJ. Bruce Fields 
242ea31a443SJ. Bruce Fields 		if (memchr(buf->data, IPV6_SCOPE_DELIMITER, buf->len))
243ea31a443SJ. Bruce Fields 			continue;
244517be09dSTrond Myklebust 
245517be09dSTrond Myklebust 		mountdata->addrlen = nfs_parse_server_name(buf->data, buf->len,
2461b340d01SStanislav Kinsbursky 				mountdata->addr, addr_bufsize,
2471b340d01SStanislav Kinsbursky 				NFS_SB(mountdata->sb));
24853a0b9c4SChuck Lever 		if (mountdata->addrlen == 0)
249ea31a443SJ. Bruce Fields 			continue;
250517be09dSTrond Myklebust 
251ec6ee612SChuck Lever 		rpc_set_port(mountdata->addr, NFS_PORT);
252ea31a443SJ. Bruce Fields 
253ef95d31eSTrond Myklebust 		memcpy(page2, buf->data, buf->len);
254ef95d31eSTrond Myklebust 		page2[buf->len] = '\0';
255ea31a443SJ. Bruce Fields 		mountdata->hostname = page2;
2564ada29d5SJ. Bruce Fields 
2574ada29d5SJ. Bruce Fields 		snprintf(page, PAGE_SIZE, "%s:%s",
2584ada29d5SJ. Bruce Fields 				mountdata->hostname,
2594ada29d5SJ. Bruce Fields 				mountdata->mnt_path);
2604ada29d5SJ. Bruce Fields 
2614ada29d5SJ. Bruce Fields 		mnt = vfs_kern_mount(&nfs4_referral_fs_type, 0, page, mountdata);
2624ada29d5SJ. Bruce Fields 		if (!IS_ERR(mnt))
2634ada29d5SJ. Bruce Fields 			break;
2644ada29d5SJ. Bruce Fields 	}
265364d015eSTrond Myklebust 	kfree(mountdata->addr);
2664ada29d5SJ. Bruce Fields 	return mnt;
2674ada29d5SJ. Bruce Fields }
2684ada29d5SJ. Bruce Fields 
269f7b422b1SDavid Howells /**
270f7b422b1SDavid Howells  * nfs_follow_referral - set up mountpoint when hitting a referral on moved error
271f7b422b1SDavid Howells  * @dentry - parent directory
2723f43c666SChuck Lever  * @locations - array of NFSv4 server location information
273f7b422b1SDavid Howells  *
274f7b422b1SDavid Howells  */
275f8ad9c4bSAl Viro static struct vfsmount *nfs_follow_referral(struct dentry *dentry,
276509de811SDavid Howells 					    const struct nfs4_fs_locations *locations)
277f7b422b1SDavid Howells {
278f7b422b1SDavid Howells 	struct vfsmount *mnt = ERR_PTR(-ENOENT);
279f7b422b1SDavid Howells 	struct nfs_clone_mount mountdata = {
280f8ad9c4bSAl Viro 		.sb = dentry->d_sb,
281f7b422b1SDavid Howells 		.dentry = dentry,
282f8ad9c4bSAl Viro 		.authflavor = NFS_SB(dentry->d_sb)->client->cl_auth->au_flavor,
283f7b422b1SDavid Howells 	};
28454ceac45SDavid Howells 	char *page = NULL, *page2 = NULL;
2853f43c666SChuck Lever 	int loc, error;
286f7b422b1SDavid Howells 
287f7b422b1SDavid Howells 	if (locations == NULL || locations->nlocations <= 0)
288f7b422b1SDavid Howells 		goto out;
289f7b422b1SDavid Howells 
2903110ff80SHarvey Harrison 	dprintk("%s: referral at %s/%s\n", __func__,
291f7b422b1SDavid Howells 		dentry->d_parent->d_name.name, dentry->d_name.name);
292f7b422b1SDavid Howells 
293f7b422b1SDavid Howells 	page = (char *) __get_free_page(GFP_USER);
29454ceac45SDavid Howells 	if (!page)
295f7b422b1SDavid Howells 		goto out;
29654ceac45SDavid Howells 
297f7b422b1SDavid Howells 	page2 = (char *) __get_free_page(GFP_USER);
29854ceac45SDavid Howells 	if (!page2)
299f7b422b1SDavid Howells 		goto out;
300f7b422b1SDavid Howells 
30154ceac45SDavid Howells 	/* Ensure fs path is a prefix of current dentry path */
302b514f872SAl Viro 	error = nfs4_validate_fspath(dentry, locations, page, page2);
30354ceac45SDavid Howells 	if (error < 0) {
30454ceac45SDavid Howells 		mnt = ERR_PTR(error);
30554ceac45SDavid Howells 		goto out;
306f7b422b1SDavid Howells 	}
307f7b422b1SDavid Howells 
308460cdbc8SJ. Bruce Fields 	for (loc = 0; loc < locations->nlocations; loc++) {
309509de811SDavid Howells 		const struct nfs4_fs_location *location = &locations->locations[loc];
310f7b422b1SDavid Howells 
311f7b422b1SDavid Howells 		if (location == NULL || location->nservers <= 0 ||
312460cdbc8SJ. Bruce Fields 		    location->rootpath.ncomponents == 0)
313f7b422b1SDavid Howells 			continue;
314f7b422b1SDavid Howells 
3154ada29d5SJ. Bruce Fields 		mnt = try_location(&mountdata, page, page2, location);
3164ada29d5SJ. Bruce Fields 		if (!IS_ERR(mnt))
317f7b422b1SDavid Howells 			break;
318f7b422b1SDavid Howells 	}
319f7b422b1SDavid Howells 
32054ceac45SDavid Howells out:
321f7b422b1SDavid Howells 	free_page((unsigned long) page);
322f7b422b1SDavid Howells 	free_page((unsigned long) page2);
3233110ff80SHarvey Harrison 	dprintk("%s: done\n", __func__);
324f7b422b1SDavid Howells 	return mnt;
325f7b422b1SDavid Howells }
326f7b422b1SDavid Howells 
327f7b422b1SDavid Howells /*
328f7b422b1SDavid Howells  * nfs_do_refmount - handle crossing a referral on server
329f7b422b1SDavid Howells  * @dentry - dentry of referral
330f7b422b1SDavid Howells  *
331f7b422b1SDavid Howells  */
332f05d147fSBryan Schumaker struct vfsmount *nfs_do_refmount(struct rpc_clnt *client, struct dentry *dentry)
333f7b422b1SDavid Howells {
33454ceac45SDavid Howells 	struct vfsmount *mnt = ERR_PTR(-ENOMEM);
335f7b422b1SDavid Howells 	struct dentry *parent;
336f7b422b1SDavid Howells 	struct nfs4_fs_locations *fs_locations = NULL;
337f7b422b1SDavid Howells 	struct page *page;
338f7b422b1SDavid Howells 	int err;
339f7b422b1SDavid Howells 
340f7b422b1SDavid Howells 	/* BUG_ON(IS_ROOT(dentry)); */
3413110ff80SHarvey Harrison 	dprintk("%s: enter\n", __func__);
342f7b422b1SDavid Howells 
343f7b422b1SDavid Howells 	page = alloc_page(GFP_KERNEL);
344f7b422b1SDavid Howells 	if (page == NULL)
345f7b422b1SDavid Howells 		goto out;
346f7b422b1SDavid Howells 
347f7b422b1SDavid Howells 	fs_locations = kmalloc(sizeof(struct nfs4_fs_locations), GFP_KERNEL);
348f7b422b1SDavid Howells 	if (fs_locations == NULL)
349f7b422b1SDavid Howells 		goto out_free;
350f7b422b1SDavid Howells 
351f7b422b1SDavid Howells 	/* Get locations */
35254ceac45SDavid Howells 	mnt = ERR_PTR(-ENOENT);
35354ceac45SDavid Howells 
354f7b422b1SDavid Howells 	parent = dget_parent(dentry);
35554ceac45SDavid Howells 	dprintk("%s: getting locations for %s/%s\n",
3563110ff80SHarvey Harrison 		__func__, parent->d_name.name, dentry->d_name.name);
35754ceac45SDavid Howells 
358f05d147fSBryan Schumaker 	err = nfs4_proc_fs_locations(client, parent->d_inode, &dentry->d_name, fs_locations, page);
359f7b422b1SDavid Howells 	dput(parent);
36054ceac45SDavid Howells 	if (err != 0 ||
36154ceac45SDavid Howells 	    fs_locations->nlocations <= 0 ||
362f7b422b1SDavid Howells 	    fs_locations->fs_path.ncomponents <= 0)
363f7b422b1SDavid Howells 		goto out_free;
364f7b422b1SDavid Howells 
365f8ad9c4bSAl Viro 	mnt = nfs_follow_referral(dentry, fs_locations);
366f7b422b1SDavid Howells out_free:
367f7b422b1SDavid Howells 	__free_page(page);
368f7b422b1SDavid Howells 	kfree(fs_locations);
369f7b422b1SDavid Howells out:
3703110ff80SHarvey Harrison 	dprintk("%s: done\n", __func__);
371f7b422b1SDavid Howells 	return mnt;
372f7b422b1SDavid Howells }
373