]> git.karo-electronics.de Git - linux-beck.git/commitdiff
NFSv4: Follow a referral
authorManoj Naik <manoj@almaden.ibm.com>
Fri, 9 Jun 2006 13:34:29 +0000 (09:34 -0400)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Fri, 9 Jun 2006 13:34:29 +0000 (09:34 -0400)
Respond to a moved error on NFS lookup by setting up the referral.
Note: We don't actually follow the referral during lookup/getattr, but
later when we detect fsid mismatch in inode revalidation (similar to the
processing done for cloning submounts). Referrals will have fake attributes
until they are actually followed or traversed.

Signed-off-by: Manoj Naik <manoj@almaden.ibm.com>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
fs/nfs/inode.c
fs/nfs/namespace.c
fs/nfs/nfs4proc.c
include/linux/nfs_fs.h
include/linux/nfs_xdr.h

index 0d8302e59d6994ffe98ae1d83b664fcb7a8a56db..ee13cb01b56ef37edf58008f585f1f71e6e9485c 100644 (file)
@@ -888,7 +888,10 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
                                set_bit(NFS_INO_ADVISE_RDPLUS, &NFS_FLAGS(inode));
                        /* Deal with crossing mountpoints */
                        if (!nfs_fsid_equal(&NFS_SB(sb)->fsid, &fattr->fsid)) {
-                               inode->i_op = &nfs_mountpoint_inode_operations;
+                               if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL)
+                                       inode->i_op = &nfs_referral_inode_operations;
+                               else
+                                       inode->i_op = &nfs_mountpoint_inode_operations;
                                inode->i_fop = NULL;
                        }
                } else if (S_ISLNK(inode->i_mode))
index e426516c1116cf829e3341e847bdc45f704e92a8..8ca44b7b25c360e96c1a568e9a55547fc0a65fd0 100644 (file)
@@ -58,7 +58,10 @@ static void * nfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd)
        if (err != 0)
                goto out_err;
 
-       mnt = nfs_do_submount(nd->mnt, nd->dentry, &fh, &fattr);
+       if (fattr.valid & NFS_ATTR_FATTR_V4_REFERRAL)
+               mnt = nfs_do_refmount(nd->mnt, nd->dentry);
+       else
+               mnt = nfs_do_submount(nd->mnt, nd->dentry, &fh, &fattr);
        err = PTR_ERR(mnt);
        if (IS_ERR(mnt))
                goto out_err;
@@ -94,6 +97,10 @@ struct inode_operations nfs_mountpoint_inode_operations = {
        .getattr        = nfs_getattr,
 };
 
+struct inode_operations nfs_referral_inode_operations = {
+       .follow_link    = nfs_follow_mountpoint,
+};
+
 static void nfs_expire_automounts(void *data)
 {
        struct list_head *list = (struct list_head *)data;
index 90ee21a07b3eda2d42c9f950c6c91fe32dfeab5b..3300e35d74ad7f68949ffea7763b17512abd6df4 100644 (file)
@@ -1462,6 +1462,50 @@ out:
        return nfs4_map_errors(status);
 }
 
+/*
+ * Get locations and (maybe) other attributes of a referral.
+ * Note that we'll actually follow the referral later when
+ * we detect fsid mismatch in inode revalidation
+ */
+static int nfs4_get_referral(struct inode *dir, struct qstr *name, struct nfs_fattr *fattr, struct nfs_fh *fhandle)
+{
+       int status = -ENOMEM;
+       struct page *page = NULL;
+       struct nfs4_fs_locations *locations = NULL;
+       struct dentry dentry = {};
+
+       page = alloc_page(GFP_KERNEL);
+       if (page == NULL)
+               goto out;
+       locations = kmalloc(sizeof(struct nfs4_fs_locations), GFP_KERNEL);
+       if (locations == NULL)
+               goto out;
+
+       dentry.d_name.name = name->name;
+       dentry.d_name.len = name->len;
+       status = nfs4_proc_fs_locations(dir, &dentry, locations, page);
+       if (status != 0)
+               goto out;
+       /* Make sure server returned a different fsid for the referral */
+       if (nfs_fsid_equal(&NFS_SERVER(dir)->fsid, &locations->fattr.fsid)) {
+               dprintk("%s: server did not return a different fsid for a referral at %s\n", __FUNCTION__, name->name);
+               status = -EIO;
+               goto out;
+       }
+
+       memcpy(fattr, &locations->fattr, sizeof(struct nfs_fattr));
+       fattr->valid |= NFS_ATTR_FATTR_V4_REFERRAL;
+       if (!fattr->mode)
+               fattr->mode = S_IFDIR;
+       memset(fhandle, 0, sizeof(struct nfs_fh));
+out:
+       if (page)
+               __free_page(page);
+       if (locations)
+               kfree(locations);
+       return status;
+}
+
 static int _nfs4_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fattr *fattr)
 {
        struct nfs4_getattr_arg args = {
@@ -1566,6 +1610,8 @@ static int _nfs4_proc_lookup(struct inode *dir, struct qstr *name,
        
        dprintk("NFS call  lookup %s\n", name->name);
        status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0);
+       if (status == -NFS4ERR_MOVED)
+               status = nfs4_get_referral(dir, name, fattr, fhandle);
        dprintk("NFS reply lookup: %d\n", status);
        return status;
 }
index 09271b10f9a8d6a4673cfa367656c46be52a5aa6..1527989491134bee1024eb922100cc3a7597d192 100644 (file)
@@ -409,6 +409,7 @@ extern void nfs_unregister_sysctl(void);
  */
 extern struct list_head nfs_automount_list;
 extern struct inode_operations nfs_mountpoint_inode_operations;
+extern struct inode_operations nfs_referral_inode_operations;
 extern int nfs_mountpoint_expiry_timeout;
 extern void nfs_release_automount_timer(void);
 
index d6eea834872862b13ce8cf536c991c202ef7924a..7c7320fa51aa013696340e90e9dfb5dc18ed00e6 100644 (file)
@@ -63,6 +63,7 @@ struct nfs_fattr {
 #define NFS_ATTR_FATTR         0x0002          /* post-op attributes */
 #define NFS_ATTR_FATTR_V3      0x0004          /* NFSv3 attributes */
 #define NFS_ATTR_FATTR_V4      0x0008          /* NFSv4 change attribute */
+#define NFS_ATTR_FATTR_V4_REFERRAL     0x0010          /* NFSv4 referral */
 
 /*
  * Info on the file system