Merge branch 'for-2.6.35' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[safe/jmp/linux-2.6] / fs / nfs / getroot.c
index 522e5ad..7428f7d 100644 (file)
@@ -30,7 +30,6 @@
 #include <linux/nfs_idmap.h>
 #include <linux/vfs.h>
 #include <linux/namei.h>
-#include <linux/mnt_namespace.h>
 #include <linux/security.h>
 
 #include <asm/system.h>
 #define NFSDBG_FACILITY                NFSDBG_CLIENT
 
 /*
+ * Set the superblock root dentry.
+ * Note that this function frees the inode in case of error.
+ */
+static int nfs_superblock_set_dummy_root(struct super_block *sb, struct inode *inode)
+{
+       /* The mntroot acts as the dummy root dentry for this superblock */
+       if (sb->s_root == NULL) {
+               sb->s_root = d_alloc_root(inode);
+               if (sb->s_root == NULL) {
+                       iput(inode);
+                       return -ENOMEM;
+               }
+               /* Circumvent igrab(): we know the inode is not being freed */
+               atomic_inc(&inode->i_count);
+               /*
+                * Ensure that this dentry is invisible to d_find_alias().
+                * Otherwise, it may be spliced into the tree by
+                * d_materialise_unique if a parent directory from the same
+                * filesystem gets mounted at a later time.
+                * This again causes shrink_dcache_for_umount_subtree() to
+                * Oops, since the test for IS_ROOT() will fail.
+                */
+               spin_lock(&dcache_lock);
+               list_del_init(&sb->s_root->d_alias);
+               spin_unlock(&dcache_lock);
+       }
+       return 0;
+}
+
+/*
  * get an NFS2/NFS3 root dentry from the root filehandle
  */
 struct dentry *nfs_get_root(struct super_block *sb, struct nfs_fh *mntfh)
 {
        struct nfs_server *server = NFS_SB(sb);
        struct nfs_fsinfo fsinfo;
-       struct nfs_fattr fattr;
-       struct dentry *mntroot;
+       struct dentry *ret;
        struct inode *inode;
        int error;
 
-       /* create a dummy root dentry with dummy inode for this superblock */
-       if (!sb->s_root) {
-               struct nfs_fh dummyfh;
-               struct dentry *root;
-               struct inode *iroot;
-
-               memset(&dummyfh, 0, sizeof(dummyfh));
-               memset(&fattr, 0, sizeof(fattr));
-               nfs_fattr_init(&fattr);
-               fattr.valid = NFS_ATTR_FATTR;
-               fattr.type = NFDIR;
-               fattr.mode = S_IFDIR | S_IRUSR | S_IWUSR;
-               fattr.nlink = 2;
-
-               iroot = nfs_fhget(sb, &dummyfh, &fattr);
-               if (IS_ERR(iroot))
-                       return ERR_PTR(PTR_ERR(iroot));
-
-               root = d_alloc_root(iroot);
-               if (!root) {
-                       iput(iroot);
-                       return ERR_PTR(-ENOMEM);
-               }
-
-               sb->s_root = root;
-       }
-
        /* get the actual root for this mount */
-       fsinfo.fattr = &fattr;
+       fsinfo.fattr = nfs_alloc_fattr();
+       if (fsinfo.fattr == NULL)
+               return ERR_PTR(-ENOMEM);
 
        error = server->nfs_client->rpc_ops->getroot(server, mntfh, &fsinfo);
        if (error < 0) {
                dprintk("nfs_get_root: getattr error = %d\n", -error);
-               return ERR_PTR(error);
+               ret = ERR_PTR(error);
+               goto out;
        }
 
        inode = nfs_fhget(sb, mntfh, fsinfo.fattr);
        if (IS_ERR(inode)) {
                dprintk("nfs_get_root: get root inode failed\n");
-               return ERR_PTR(PTR_ERR(inode));
+               ret = ERR_CAST(inode);
+               goto out;
+       }
+
+       error = nfs_superblock_set_dummy_root(sb, inode);
+       if (error != 0) {
+               ret = ERR_PTR(error);
+               goto out;
        }
 
        /* root dentries normally start off anonymous and get spliced in later
         * if the dentry tree reaches them; however if the dentry already
         * exists, we'll pick it up at this point and use it as the root
         */
-       mntroot = d_alloc_anon(inode);
-       if (!mntroot) {
-               iput(inode);
+       ret = d_obtain_alias(inode);
+       if (IS_ERR(ret)) {
                dprintk("nfs_get_root: get root dentry failed\n");
-               return ERR_PTR(-ENOMEM);
+               goto out;
        }
 
-       security_d_instantiate(mntroot, inode);
-
-       if (!mntroot->d_op)
-               mntroot->d_op = server->nfs_client->rpc_ops->dentry_ops;
+       security_d_instantiate(ret, inode);
 
-       return mntroot;
+       if (ret->d_op == NULL)
+               ret->d_op = server->nfs_client->rpc_ops->dentry_ops;
+out:
+       nfs_free_fattr(fsinfo.fattr);
+       return ret;
 }
 
 #ifdef CONFIG_NFS_V4
 
-/*
- * Do a simple pathwalk from the root FH of the server to the nominated target
- * of the mountpoint
- * - give error on symlinks
- * - give error on ".." occurring in the path
- * - follow traversals
- */
-int nfs4_path_walk(struct nfs_server *server,
-                  struct nfs_fh *mntfh,
-                  const char *path)
+int nfs4_get_rootfh(struct nfs_server *server, struct nfs_fh *mntfh)
 {
        struct nfs_fsinfo fsinfo;
-       struct nfs_fattr fattr;
-       struct nfs_fh lastfh;
-       struct qstr name;
-       int ret;
-
-       dprintk("--> nfs4_path_walk(,,%s)\n", path);
+       int ret = -ENOMEM;
 
-       fsinfo.fattr = &fattr;
-       nfs_fattr_init(&fattr);
+       dprintk("--> nfs4_get_rootfh()\n");
 
-       /* Eat leading slashes */
-       while (*path == '/')
-               path++;
+       fsinfo.fattr = nfs_alloc_fattr();
+       if (fsinfo.fattr == NULL)
+               goto out;
 
        /* Start by getting the root filehandle from the server */
        ret = server->nfs_client->rpc_ops->getroot(server, mntfh, &fsinfo);
        if (ret < 0) {
-               dprintk("nfs4_get_root: getroot error = %d\n", -ret);
-               return ret;
+               dprintk("nfs4_get_rootfh: getroot error = %d\n", -ret);
+               goto out;
        }
 
-       if (fattr.type != NFDIR) {
-               printk(KERN_ERR "nfs4_get_root:"
+       if (!(fsinfo.fattr->valid & NFS_ATTR_FATTR_MODE)
+                       || !S_ISDIR(fsinfo.fattr->mode)) {
+               printk(KERN_ERR "nfs4_get_rootfh:"
                       " getroot encountered non-directory\n");
-               return -ENOTDIR;
+               ret = -ENOTDIR;
+               goto out;
        }
 
-       /* FIXME: It is quite valid for the server to return a referral here */
-       if (fattr.valid & NFS_ATTR_FATTR_V4_REFERRAL) {
-               printk(KERN_ERR "nfs4_get_root:"
+       if (fsinfo.fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL) {
+               printk(KERN_ERR "nfs4_get_rootfh:"
                       " getroot obtained referral\n");
-               return -EREMOTE;
-       }
-
-next_component:
-       dprintk("Next: %s\n", path);
-
-       /* extract the next bit of the path */
-       if (!*path)
-               goto path_walk_complete;
-
-       name.name = path;
-       while (*path && *path != '/')
-               path++;
-       name.len = path - (const char *) name.name;
-
-       if (name.len > NFS4_MAXNAMLEN)
-               return -ENAMETOOLONG;
-
-eat_dot_dir:
-       while (*path == '/')
-               path++;
-
-       if (path[0] == '.' && (path[1] == '/' || !path[1])) {
-               path += 2;
-               goto eat_dot_dir;
+               ret = -EREMOTE;
+               goto out;
        }
 
-       /* FIXME: Why shouldn't the user be able to use ".." in the path? */
-       if (path[0] == '.' && path[1] == '.' && (path[2] == '/' || !path[2])
-           ) {
-               printk(KERN_ERR "nfs4_get_root:"
-                      " Mount path contains reference to \"..\"\n");
-               return -EINVAL;
-       }
-
-       /* lookup the next FH in the sequence */
-       memcpy(&lastfh, mntfh, sizeof(lastfh));
-
-       dprintk("LookupFH: %*.*s [%s]\n", name.len, name.len, name.name, path);
-
-       ret = server->nfs_client->rpc_ops->lookupfh(server, &lastfh, &name,
-                                                   mntfh, &fattr);
-       if (ret < 0) {
-               dprintk("nfs4_get_root: getroot error = %d\n", -ret);
-               return ret;
-       }
-
-       if (fattr.type != NFDIR) {
-               printk(KERN_ERR "nfs4_get_root:"
-                      " lookupfh encountered non-directory\n");
-               return -ENOTDIR;
-       }
-
-       /* FIXME: Referrals are quite valid here too */
-       if (fattr.valid & NFS_ATTR_FATTR_V4_REFERRAL) {
-               printk(KERN_ERR "nfs4_get_root:"
-                      " lookupfh obtained referral\n");
-               return -EREMOTE;
-       }
-
-       goto next_component;
-
-path_walk_complete:
-       memcpy(&server->fsid, &fattr.fsid, sizeof(server->fsid));
-       dprintk("<-- nfs4_path_walk() = 0\n");
-       return 0;
+       memcpy(&server->fsid, &fsinfo.fattr->fsid, sizeof(server->fsid));
+out:
+       nfs_free_fattr(fsinfo.fattr);
+       dprintk("<-- nfs4_get_rootfh() = %d\n", ret);
+       return ret;
 }
 
 /*
@@ -234,40 +174,13 @@ path_walk_complete:
 struct dentry *nfs4_get_root(struct super_block *sb, struct nfs_fh *mntfh)
 {
        struct nfs_server *server = NFS_SB(sb);
-       struct nfs_fattr fattr;
-       struct dentry *mntroot;
+       struct nfs_fattr *fattr = NULL;
+       struct dentry *ret;
        struct inode *inode;
        int error;
 
        dprintk("--> nfs4_get_root()\n");
 
-       /* create a dummy root dentry with dummy inode for this superblock */
-       if (!sb->s_root) {
-               struct nfs_fh dummyfh;
-               struct dentry *root;
-               struct inode *iroot;
-
-               memset(&dummyfh, 0, sizeof(dummyfh));
-               memset(&fattr, 0, sizeof(fattr));
-               nfs_fattr_init(&fattr);
-               fattr.valid = NFS_ATTR_FATTR;
-               fattr.type = NFDIR;
-               fattr.mode = S_IFDIR | S_IRUSR | S_IWUSR;
-               fattr.nlink = 2;
-
-               iroot = nfs_fhget(sb, &dummyfh, &fattr);
-               if (IS_ERR(iroot))
-                       return ERR_PTR(PTR_ERR(iroot));
-
-               root = d_alloc_root(iroot);
-               if (!root) {
-                       iput(iroot);
-                       return ERR_PTR(-ENOMEM);
-               }
-
-               sb->s_root = root;
-       }
-
        /* get the info about the server and filesystem */
        error = nfs4_server_capabilities(server, mntfh);
        if (error < 0) {
@@ -276,37 +189,50 @@ struct dentry *nfs4_get_root(struct super_block *sb, struct nfs_fh *mntfh)
                return ERR_PTR(error);
        }
 
+       fattr = nfs_alloc_fattr();
+       if (fattr == NULL)
+               return ERR_PTR(-ENOMEM);;
+
        /* get the actual root for this mount */
-       error = server->nfs_client->rpc_ops->getattr(server, mntfh, &fattr);
+       error = server->nfs_client->rpc_ops->getattr(server, mntfh, fattr);
        if (error < 0) {
                dprintk("nfs_get_root: getattr error = %d\n", -error);
-               return ERR_PTR(error);
+               ret = ERR_PTR(error);
+               goto out;
        }
 
-       inode = nfs_fhget(sb, mntfh, &fattr);
+       inode = nfs_fhget(sb, mntfh, fattr);
        if (IS_ERR(inode)) {
                dprintk("nfs_get_root: get root inode failed\n");
-               return ERR_PTR(PTR_ERR(inode));
+               ret = ERR_CAST(inode);
+               goto out;
+       }
+
+       error = nfs_superblock_set_dummy_root(sb, inode);
+       if (error != 0) {
+               ret = ERR_PTR(error);
+               goto out;
        }
 
        /* root dentries normally start off anonymous and get spliced in later
         * if the dentry tree reaches them; however if the dentry already
         * exists, we'll pick it up at this point and use it as the root
         */
-       mntroot = d_alloc_anon(inode);
-       if (!mntroot) {
-               iput(inode);
+       ret = d_obtain_alias(inode);
+       if (IS_ERR(ret)) {
                dprintk("nfs_get_root: get root dentry failed\n");
-               return ERR_PTR(-ENOMEM);
+               goto out;
        }
 
-       security_d_instantiate(mntroot, inode);
+       security_d_instantiate(ret, inode);
 
-       if (!mntroot->d_op)
-               mntroot->d_op = server->nfs_client->rpc_ops->dentry_ops;
+       if (ret->d_op == NULL)
+               ret->d_op = server->nfs_client->rpc_ops->dentry_ops;
 
+out:
+       nfs_free_fattr(fattr);
        dprintk("<-- nfs4_get_root()\n");
-       return mntroot;
+       return ret;
 }
 
 #endif /* CONFIG_NFS_V4 */