NFS is the most complicated of the lot.  It uses ->d_fsdata to

 1) store devname in the root dentry, and
 2) store nfs_unlinkdata for sillyrenames.

In addition it stores a verifier in ->d_time.

Introduce nfs_dentry structure that to store all of the above and make
d_fsdata point to it.  Need to use rcu freeing since ->verf is accessed
from dentry revalidation.  And not only read, but changed as well, so
rcu_head must not overlay verf.

Signed-off-by: Miklos Szeredi <mszer...@redhat.com>
Cc: Trond Myklebust <trond.mykleb...@primarydata.com>
---
 fs/nfs/dir.c           | 18 ++++++++++++++----
 fs/nfs/getroot.c       |  4 ++--
 fs/nfs/namespace.c     |  2 +-
 fs/nfs/unlink.c        | 16 ++++++++--------
 include/linux/nfs_fs.h | 19 ++++++++++++++++++-
 5 files changed, 43 insertions(+), 16 deletions(-)

diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 5f1af4cd1a33..5a3f02c4c28a 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1041,7 +1041,7 @@ static int nfs_check_verifier(struct inode *dir, struct 
dentry *dentry,
                return 1;
        if (NFS_SERVER(dir)->flags & NFS_MOUNT_LOOKUP_CACHE_NONE)
                return 0;
-       if (!nfs_verify_change_attribute(dir, dentry->d_time))
+       if (!nfs_verify_change_attribute(dir, NFS_D(dentry)->verf))
                return 0;
        /* Revalidate nfsi->cache_change_attribute before we declare a match */
        if (rcu_walk)
@@ -1050,7 +1050,7 @@ static int nfs_check_verifier(struct inode *dir, struct 
dentry *dentry,
                ret = nfs_revalidate_inode(NFS_SERVER(dir), dir);
        if (ret < 0)
                return 0;
-       if (!nfs_verify_change_attribute(dir, dentry->d_time))
+       if (!nfs_verify_change_attribute(dir, NFS_D(dentry)->verf))
                return 0;
        return 1;
 }
@@ -1358,15 +1358,23 @@ static void nfs_dentry_iput(struct dentry *dentry, 
struct inode *inode)
        iput(inode);
 }
 
+static int nfs_d_init(struct dentry *dentry)
+{
+       dentry->d_fsdata = kzalloc(sizeof(struct nfs_dentry), GFP_KERNEL);
+
+       return dentry->d_fsdata ? 0 : -ENOMEM;
+}
+
 static void nfs_d_release(struct dentry *dentry)
 {
        /* free cached devname value, if it survived that far */
-       if (unlikely(dentry->d_fsdata)) {
+       if (unlikely(NFS_D(dentry)->devname)) {
                if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
                        WARN_ON(1);
                else
-                       kfree(dentry->d_fsdata);
+                       kfree(NFS_D(dentry)->devname);
        }
+       kfree_rcu(NFS_D(dentry), rcu);
 }
 
 const struct dentry_operations nfs_dentry_operations = {
@@ -1375,6 +1383,7 @@ const struct dentry_operations nfs_dentry_operations = {
        .d_delete       = nfs_dentry_delete,
        .d_iput         = nfs_dentry_iput,
        .d_automount    = nfs_d_automount,
+       .d_init         = nfs_d_init,
        .d_release      = nfs_d_release,
 };
 EXPORT_SYMBOL_GPL(nfs_dentry_operations);
@@ -1453,6 +1462,7 @@ const struct dentry_operations nfs4_dentry_operations = {
        .d_delete       = nfs_dentry_delete,
        .d_iput         = nfs_dentry_iput,
        .d_automount    = nfs_d_automount,
+       .d_init         = nfs_d_init,
        .d_release      = nfs_d_release,
 };
 EXPORT_SYMBOL_GPL(nfs4_dentry_operations);
diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
index a608ffd28acc..84482c656e05 100644
--- a/fs/nfs/getroot.c
+++ b/fs/nfs/getroot.c
@@ -120,9 +120,9 @@ struct dentry *nfs_get_root(struct super_block *sb, struct 
nfs_fh *mntfh,
 
        security_d_instantiate(ret, inode);
        spin_lock(&ret->d_lock);
-       if (IS_ROOT(ret) && !ret->d_fsdata &&
+       if (IS_ROOT(ret) && !NFS_D(ret)->devname &&
            !(ret->d_flags & DCACHE_NFSFS_RENAMED)) {
-               ret->d_fsdata = name;
+               NFS_D(ret)->devname = name;
                name = NULL;
        }
        spin_unlock(&ret->d_lock);
diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
index c8162c660c44..0060806d047b 100644
--- a/fs/nfs/namespace.c
+++ b/fs/nfs/namespace.c
@@ -90,7 +90,7 @@ char *nfs_path(char **p, struct dentry *dentry, char *buffer, 
ssize_t buflen,
                *--end = '/';
        }
        *p = end;
-       base = dentry->d_fsdata;
+       base = NFS_D(dentry)->devname;
        if (!base) {
                spin_unlock(&dentry->d_lock);
                rcu_read_unlock();
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index 191aa577dd1f..74e708fbd945 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -134,8 +134,8 @@ static int nfs_call_unlink(struct dentry *dentry, struct 
nfs_unlinkdata *data)
                spin_lock(&alias->d_lock);
                if (d_really_is_positive(alias) &&
                    !(alias->d_flags & DCACHE_NFSFS_RENAMED)) {
-                       devname_garbage = alias->d_fsdata;
-                       alias->d_fsdata = data;
+                       devname_garbage = NFS_D(alias)->devname;
+                       NFS_D(alias)->data = data;
                        alias->d_flags |= DCACHE_NFSFS_RENAMED;
                        ret = 1;
                } else
@@ -189,8 +189,8 @@ nfs_async_unlink(struct dentry *dentry, const struct qstr 
*name)
        if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
                goto out_unlock;
        dentry->d_flags |= DCACHE_NFSFS_RENAMED;
-       devname_garbage = dentry->d_fsdata;
-       dentry->d_fsdata = data;
+       devname_garbage = NFS_D(dentry)->devname;
+       NFS_D(dentry)->data = data;
        spin_unlock(&dentry->d_lock);
        /*
         * If we'd displaced old cached devname, free it.  At that
@@ -226,8 +226,8 @@ nfs_complete_unlink(struct dentry *dentry, struct inode 
*inode)
 
        spin_lock(&dentry->d_lock);
        dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
-       data = dentry->d_fsdata;
-       dentry->d_fsdata = NULL;
+       data = NFS_D(dentry)->data;
+       NFS_D(dentry)->data = NULL;
        spin_unlock(&dentry->d_lock);
 
        if (NFS_STALE(inode) || !nfs_call_unlink(dentry, data))
@@ -240,10 +240,10 @@ nfs_cancel_async_unlink(struct dentry *dentry)
 {
        spin_lock(&dentry->d_lock);
        if (dentry->d_flags & DCACHE_NFSFS_RENAMED) {
-               struct nfs_unlinkdata *data = dentry->d_fsdata;
+               struct nfs_unlinkdata *data = NFS_D(dentry)->data;
 
                dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
-               dentry->d_fsdata = NULL;
+               NFS_D(dentry)->data = NULL;
                spin_unlock(&dentry->d_lock);
                nfs_free_unlinkdata(data);
                return;
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index 810124b33327..20a07688a392 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -187,6 +187,18 @@ struct nfs_inode {
 };
 
 /*
+ * NFS specific dentry data
+ */
+struct nfs_dentry {
+       union {
+               char *devname;
+               struct nfs_unlinkdata *data;
+               struct rcu_head rcu;
+       };
+       unsigned long verf;
+};
+
+/*
  * Cache validity bit flags
  */
 #define NFS_INO_INVALID_ATTR   0x0001          /* cached attrs are invalid */
@@ -217,6 +229,11 @@ static inline struct nfs_inode *NFS_I(const struct inode 
*inode)
        return container_of(inode, struct nfs_inode, vfs_inode);
 }
 
+static inline struct nfs_dentry *NFS_D(struct dentry *dentry)
+{
+       return (struct nfs_dentry *) dentry->d_fsdata;
+}
+
 static inline struct nfs_server *NFS_SB(const struct super_block *s)
 {
        return (struct nfs_server *)(s->s_fs_info);
@@ -299,7 +316,7 @@ static inline int nfs_server_capable(struct inode *inode, 
int cap)
 
 static inline void nfs_set_verifier(struct dentry * dentry, unsigned long verf)
 {
-       dentry->d_time = verf;
+       NFS_D(dentry)->verf = verf;
 }
 
 /**
-- 
2.5.5

Reply via email to