summaryrefslogtreecommitdiff
path: root/fs/nfs
diff options
context:
space:
mode:
Diffstat (limited to 'fs/nfs')
-rw-r--r--fs/nfs/inode.c5
-rw-r--r--fs/nfs/namespace.c9
-rw-r--r--fs/nfs/nfs4proc.c46
3 files changed, 58 insertions, 2 deletions
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 0d8302e59d69..ee13cb01b56e 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -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))
diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
index e426516c1116..8ca44b7b25c3 100644
--- a/fs/nfs/namespace.c
+++ b/fs/nfs/namespace.c
@@ -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;
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 90ee21a07b3e..3300e35d74ad 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -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;
}