aboutsummaryrefslogtreecommitdiffstats
path: root/fs/nfs/getroot.c
diff options
context:
space:
mode:
authorDavid Howells <dhowells@redhat.com>2006-08-22 20:06:13 -0400
committerTrond Myklebust <Trond.Myklebust@netapp.com>2006-09-22 23:24:37 -0400
commit54ceac4515986030c2502960be620198dd8fe25b (patch)
treeb4ae4305c5652c0fe883ef5ea3243da91dbd2b34 /fs/nfs/getroot.c
parentcf6d7b5de8535a9f0088c5cc28ee2dae87371b4a (diff)
NFS: Share NFS superblocks per-protocol per-server per-FSID
The attached patch makes NFS share superblocks between mounts from the same server and FSID over the same protocol. It does this by creating each superblock with a false root and returning the real root dentry in the vfsmount presented by get_sb(). The root dentry set starts off as an anonymous dentry if we don't already have the dentry for its inode, otherwise it simply returns the dentry we already have. We may thus end up with several trees of dentries in the superblock, and if at some later point one of anonymous tree roots is discovered by normal filesystem activity to be located in another tree within the superblock, the anonymous root is named and materialises attached to the second tree at the appropriate point. Why do it this way? Why not pass an extra argument to the mount() syscall to indicate the subpath and then pathwalk from the server root to the desired directory? You can't guarantee this will work for two reasons: (1) The root and intervening nodes may not be accessible to the client. With NFS2 and NFS3, for instance, mountd is called on the server to get the filehandle for the tip of a path. mountd won't give us handles for anything we don't have permission to access, and so we can't set up NFS inodes for such nodes, and so can't easily set up dentries (we'd have to have ghost inodes or something). With this patch we don't actually create dentries until we get handles from the server that we can use to set up their inodes, and we don't actually bind them into the tree until we know for sure where they go. (2) Inaccessible symbolic links. If we're asked to mount two exports from the server, eg: mount warthog:/warthog/aaa/xxx /mmm mount warthog:/warthog/bbb/yyy /nnn We may not be able to access anything nearer the root than xxx and yyy, but we may find out later that /mmm/www/yyy, say, is actually the same directory as the one mounted on /nnn. What we might then find out, for example, is that /warthog/bbb was actually a symbolic link to /warthog/aaa/xxx/www, but we can't actually determine that by talking to the server until /warthog is made available by NFS. This would lead to having constructed an errneous dentry tree which we can't easily fix. We can end up with a dentry marked as a directory when it should actually be a symlink, or we could end up with an apparently hardlinked directory. With this patch we need not make assumptions about the type of a dentry for which we can't retrieve information, nor need we assume we know its place in the grand scheme of things until we actually see that place. This patch reduces the possibility of aliasing in the inode and page caches for inodes that may be accessed by more than one NFS export. It also reduces the number of superblocks required for NFS where there are many NFS exports being used from a server (home directory server + autofs for example). This in turn makes it simpler to do local caching of network filesystems, as it can then be guaranteed that there won't be links from multiple inodes in separate superblocks to the same cache file. Obviously, cache aliasing between different levels of NFS protocol could still be a problem, but at least that gives us another key to use when indexing the cache. This patch makes the following changes: (1) The server record construction/destruction has been abstracted out into its own set of functions to make things easier to get right. These have been moved into fs/nfs/client.c. All the code in fs/nfs/client.c has to do with the management of connections to servers, and doesn't touch superblocks in any way; the remaining code in fs/nfs/super.c has to do with VFS superblock management. (2) The sequence of events undertaken by NFS mount is now reordered: (a) A volume representation (struct nfs_server) is allocated. (b) A server representation (struct nfs_client) is acquired. This may be allocated or shared, and is keyed on server address, port and NFS version. (c) If allocated, the client representation is initialised. The state member variable of nfs_client is used to prevent a race during initialisation from two mounts. (d) For NFS4 a simple pathwalk is performed, walking from FH to FH to find the root filehandle for the mount (fs/nfs/getroot.c). For NFS2/3 we are given the root FH in advance. (e) The volume FSID is probed for on the root FH. (f) The volume representation is initialised from the FSINFO record retrieved on the root FH. (g) sget() is called to acquire a superblock. This may be allocated or shared, keyed on client pointer and FSID. (h) If allocated, the superblock is initialised. (i) If the superblock is shared, then the new nfs_server record is discarded. (j) The root dentry for this mount is looked up from the root FH. (k) The root dentry for this mount is assigned to the vfsmount. (3) nfs_readdir_lookup() creates dentries for each of the entries readdir() returns; this function now attaches disconnected trees from alternate roots that happen to be discovered attached to a directory being read (in the same way nfs_lookup() is made to do for lookup ops). The new d_materialise_unique() function is now used to do this, thus permitting the whole thing to be done under one set of locks, and thus avoiding any race between mount and lookup operations on the same directory. (4) The client management code uses a new debug facility: NFSDBG_CLIENT which is set by echoing 1024 to /proc/net/sunrpc/nfs_debug. (5) Clone mounts are now called xdev mounts. (6) Use the dentry passed to the statfs() op as the handle for retrieving fs statistics rather than the root dentry of the superblock (which is now a dummy). Signed-Off-By: David Howells <dhowells@redhat.com> Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfs/getroot.c')
-rw-r--r--fs/nfs/getroot.c306
1 files changed, 306 insertions, 0 deletions
diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
new file mode 100644
index 000000000000..977e59088eeb
--- /dev/null
+++ b/fs/nfs/getroot.c
@@ -0,0 +1,306 @@
1/* getroot.c: get the root dentry for an NFS mount
2 *
3 * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved.
4 * Written by David Howells (dhowells@redhat.com)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 */
11
12#include <linux/config.h>
13#include <linux/module.h>
14#include <linux/init.h>
15
16#include <linux/time.h>
17#include <linux/kernel.h>
18#include <linux/mm.h>
19#include <linux/string.h>
20#include <linux/stat.h>
21#include <linux/errno.h>
22#include <linux/unistd.h>
23#include <linux/sunrpc/clnt.h>
24#include <linux/sunrpc/stats.h>
25#include <linux/nfs_fs.h>
26#include <linux/nfs_mount.h>
27#include <linux/nfs4_mount.h>
28#include <linux/lockd/bind.h>
29#include <linux/smp_lock.h>
30#include <linux/seq_file.h>
31#include <linux/mount.h>
32#include <linux/nfs_idmap.h>
33#include <linux/vfs.h>
34#include <linux/namei.h>
35#include <linux/namespace.h>
36
37#include <asm/system.h>
38#include <asm/uaccess.h>
39
40#include "nfs4_fs.h"
41#include "delegation.h"
42#include "internal.h"
43
44#define NFSDBG_FACILITY NFSDBG_CLIENT
45#define NFS_PARANOIA 1
46
47/*
48 * get an NFS2/NFS3 root dentry from the root filehandle
49 */
50struct dentry *nfs_get_root(struct super_block *sb, struct nfs_fh *mntfh)
51{
52 struct nfs_server *server = NFS_SB(sb);
53 struct nfs_fsinfo fsinfo;
54 struct nfs_fattr fattr;
55 struct dentry *mntroot;
56 struct inode *inode;
57 int error;
58
59 /* create a dummy root dentry with dummy inode for this superblock */
60 if (!sb->s_root) {
61 struct nfs_fh dummyfh;
62 struct dentry *root;
63 struct inode *iroot;
64
65 memset(&dummyfh, 0, sizeof(dummyfh));
66 memset(&fattr, 0, sizeof(fattr));
67 nfs_fattr_init(&fattr);
68 fattr.valid = NFS_ATTR_FATTR;
69 fattr.type = NFDIR;
70 fattr.mode = S_IFDIR | S_IRUSR | S_IWUSR;
71 fattr.nlink = 2;
72
73 iroot = nfs_fhget(sb, &dummyfh, &fattr);
74 if (IS_ERR(iroot))
75 return ERR_PTR(PTR_ERR(iroot));
76
77 root = d_alloc_root(iroot);
78 if (!root) {
79 iput(iroot);
80 return ERR_PTR(-ENOMEM);
81 }
82
83 sb->s_root = root;
84 }
85
86 /* get the actual root for this mount */
87 fsinfo.fattr = &fattr;
88
89 error = server->nfs_client->rpc_ops->getroot(server, mntfh, &fsinfo);
90 if (error < 0) {
91 dprintk("nfs_get_root: getattr error = %d\n", -error);
92 return ERR_PTR(error);
93 }
94
95 inode = nfs_fhget(sb, mntfh, fsinfo.fattr);
96 if (IS_ERR(inode)) {
97 dprintk("nfs_get_root: get root inode failed\n");
98 return ERR_PTR(PTR_ERR(inode));
99 }
100
101 /* root dentries normally start off anonymous and get spliced in later
102 * if the dentry tree reaches them; however if the dentry already
103 * exists, we'll pick it up at this point and use it as the root
104 */
105 mntroot = d_alloc_anon(inode);
106 if (!mntroot) {
107 iput(inode);
108 dprintk("nfs_get_root: get root dentry failed\n");
109 return ERR_PTR(-ENOMEM);
110 }
111
112 if (!mntroot->d_op)
113 mntroot->d_op = server->nfs_client->rpc_ops->dentry_ops;
114
115 return mntroot;
116}
117
118#ifdef CONFIG_NFS_V4
119
120/*
121 * Do a simple pathwalk from the root FH of the server to the nominated target
122 * of the mountpoint
123 * - give error on symlinks
124 * - give error on ".." occurring in the path
125 * - follow traversals
126 */
127int nfs4_path_walk(struct nfs_server *server,
128 struct nfs_fh *mntfh,
129 const char *path)
130{
131 struct nfs_fsinfo fsinfo;
132 struct nfs_fattr fattr;
133 struct nfs_fh lastfh;
134 struct qstr name;
135 int ret;
136 //int referral_count = 0;
137
138 dprintk("--> nfs4_path_walk(,,%s)\n", path);
139
140 fsinfo.fattr = &fattr;
141 nfs_fattr_init(&fattr);
142
143 if (*path++ != '/') {
144 dprintk("nfs4_get_root: Path does not begin with a slash\n");
145 return -EINVAL;
146 }
147
148 /* Start by getting the root filehandle from the server */
149 ret = server->nfs_client->rpc_ops->getroot(server, mntfh, &fsinfo);
150 if (ret < 0) {
151 dprintk("nfs4_get_root: getroot error = %d\n", -ret);
152 return ret;
153 }
154
155 if (fattr.type != NFDIR) {
156 printk(KERN_ERR "nfs4_get_root:"
157 " getroot encountered non-directory\n");
158 return -ENOTDIR;
159 }
160
161 if (fattr.valid & NFS_ATTR_FATTR_V4_REFERRAL) {
162 printk(KERN_ERR "nfs4_get_root:"
163 " getroot obtained referral\n");
164 return -EREMOTE;
165 }
166
167next_component:
168 dprintk("Next: %s\n", path);
169
170 /* extract the next bit of the path */
171 if (!*path)
172 goto path_walk_complete;
173
174 name.name = path;
175 while (*path && *path != '/')
176 path++;
177 name.len = path - (const char *) name.name;
178
179eat_dot_dir:
180 while (*path == '/')
181 path++;
182
183 if (path[0] == '.' && (path[1] == '/' || !path[1])) {
184 path += 2;
185 goto eat_dot_dir;
186 }
187
188 if (path[0] == '.' && path[1] == '.' && (path[2] == '/' || !path[2])
189 ) {
190 printk(KERN_ERR "nfs4_get_root:"
191 " Mount path contains reference to \"..\"\n");
192 return -EINVAL;
193 }
194
195 /* lookup the next FH in the sequence */
196 memcpy(&lastfh, mntfh, sizeof(lastfh));
197
198 dprintk("LookupFH: %*.*s [%s]\n", name.len, name.len, name.name, path);
199
200 ret = server->nfs_client->rpc_ops->lookupfh(server, &lastfh, &name,
201 mntfh, &fattr);
202 if (ret < 0) {
203 dprintk("nfs4_get_root: getroot error = %d\n", -ret);
204 return ret;
205 }
206
207 if (fattr.type != NFDIR) {
208 printk(KERN_ERR "nfs4_get_root:"
209 " lookupfh encountered non-directory\n");
210 return -ENOTDIR;
211 }
212
213 if (fattr.valid & NFS_ATTR_FATTR_V4_REFERRAL) {
214 printk(KERN_ERR "nfs4_get_root:"
215 " lookupfh obtained referral\n");
216 return -EREMOTE;
217 }
218
219 goto next_component;
220
221path_walk_complete:
222 memcpy(&server->fsid, &fattr.fsid, sizeof(server->fsid));
223 dprintk("<-- nfs4_path_walk() = 0\n");
224 return 0;
225}
226
227/*
228 * get an NFS4 root dentry from the root filehandle
229 */
230struct dentry *nfs4_get_root(struct super_block *sb, struct nfs_fh *mntfh)
231{
232 struct nfs_server *server = NFS_SB(sb);
233 struct nfs_fattr fattr;
234 struct dentry *mntroot;
235 struct inode *inode;
236 int error;
237
238 dprintk("--> nfs4_get_root()\n");
239
240 /* create a dummy root dentry with dummy inode for this superblock */
241 if (!sb->s_root) {
242 struct nfs_fh dummyfh;
243 struct dentry *root;
244 struct inode *iroot;
245
246 memset(&dummyfh, 0, sizeof(dummyfh));
247 memset(&fattr, 0, sizeof(fattr));
248 nfs_fattr_init(&fattr);
249 fattr.valid = NFS_ATTR_FATTR;
250 fattr.type = NFDIR;
251 fattr.mode = S_IFDIR | S_IRUSR | S_IWUSR;
252 fattr.nlink = 2;
253
254 iroot = nfs_fhget(sb, &dummyfh, &fattr);
255 if (IS_ERR(iroot))
256 return ERR_PTR(PTR_ERR(iroot));
257
258 root = d_alloc_root(iroot);
259 if (!root) {
260 iput(iroot);
261 return ERR_PTR(-ENOMEM);
262 }
263
264 sb->s_root = root;
265 }
266
267 /* get the info about the server and filesystem */
268 error = nfs4_server_capabilities(server, mntfh);
269 if (error < 0) {
270 dprintk("nfs_get_root: getcaps error = %d\n",
271 -error);
272 return ERR_PTR(error);
273 }
274
275 /* get the actual root for this mount */
276 error = server->nfs_client->rpc_ops->getattr(server, mntfh, &fattr);
277 if (error < 0) {
278 dprintk("nfs_get_root: getattr error = %d\n", -error);
279 return ERR_PTR(error);
280 }
281
282 inode = nfs_fhget(sb, mntfh, &fattr);
283 if (IS_ERR(inode)) {
284 dprintk("nfs_get_root: get root inode failed\n");
285 return ERR_PTR(PTR_ERR(inode));
286 }
287
288 /* root dentries normally start off anonymous and get spliced in later
289 * if the dentry tree reaches them; however if the dentry already
290 * exists, we'll pick it up at this point and use it as the root
291 */
292 mntroot = d_alloc_anon(inode);
293 if (!mntroot) {
294 iput(inode);
295 dprintk("nfs_get_root: get root dentry failed\n");
296 return ERR_PTR(-ENOMEM);
297 }
298
299 if (!mntroot->d_op)
300 mntroot->d_op = server->nfs_client->rpc_ops->dentry_ops;
301
302 dprintk("<-- nfs4_get_root()\n");
303 return mntroot;
304}
305
306#endif /* CONFIG_NFS_V4 */