VFS: Avoid dentry aliasing problems in filesystems like NFS, where inodes may be marked as stale in one instance (causing the dentry to be dropped) then re-enabled in the next instance. Signed-off-by: Trond Myklebust --- fs/dcache.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ fs/nfs/dir.c | 41 +++++++++++++++++++++++++---------------- include/linux/dcache.h | 18 ++++++++++++++++++ 3 files changed, 91 insertions(+), 16 deletions(-) Index: linux-2.6.10-rc1-up/include/linux/dcache.h =================================================================== --- linux-2.6.10-rc1-up.orig/include/linux/dcache.h 2004-10-23 17:16:26.445896285 -0400 +++ linux-2.6.10-rc1-up/include/linux/dcache.h 2004-10-23 18:02:47.292549981 -0400 @@ -199,6 +199,7 @@ static inline int dname_external(struct * These are the low-level FS interfaces to the dcache.. */ extern void d_instantiate(struct dentry *, struct inode *); +extern struct dentry * d_instantiate_unique(struct dentry *, struct inode *); extern void d_delete(struct dentry *); /* allocate/de-allocate */ @@ -242,6 +243,23 @@ static inline void d_add(struct dentry * d_rehash(entry); } +/** + * d_add_unique - add dentry to hash queues without aliasing + * @entry: dentry to add + * @inode: The inode to attach to this dentry + * + * This adds the entry to the hash queues and initializes @inode. + * The entry was actually filled in earlier during d_alloc(). + */ +static inline struct dentry *d_add_unique(struct dentry *entry, struct inode *inode) +{ + struct dentry *res; + + res = d_instantiate_unique(entry, inode); + d_rehash(res != NULL ? res : entry); + return res; +} + /* used for rename() and baskets */ extern void d_move(struct dentry *, struct dentry *); Index: linux-2.6.10-rc1-up/fs/dcache.c =================================================================== --- linux-2.6.10-rc1-up.orig/fs/dcache.c 2004-10-23 17:15:30.000000000 -0400 +++ linux-2.6.10-rc1-up/fs/dcache.c 2004-10-23 18:02:47.346542858 -0400 @@ -770,6 +770,54 @@ void d_instantiate(struct dentry *entry, } /** + * d_instantiate_unique - instantiate a non-aliased dentry + * @entry: dentry to instantiate + * @inode: inode to attach to this dentry + * + * Fill in inode information in the entry. On success, it returns NULL. + * If an unhashed alias of "entry" already exists, then we return the + * aliased dentry instead. + * + * Note that in order to avoid conflicts with rename() etc, the caller + * had better be holding the parent directory semaphore. + */ +struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode) +{ + struct dentry *alias; + int len = entry->d_name.len; + const char *name = entry->d_name.name; + unsigned int hash = entry->d_name.hash; + + BUG_ON(!list_empty(&entry->d_alias)); + spin_lock(&dcache_lock); + if (!inode) + goto do_negative; + list_for_each_entry(alias, &inode->i_dentry, d_alias) { + struct qstr *qstr = &alias->d_name; + + if (qstr->hash != hash) + continue; + if (alias->d_parent != entry->d_parent) + continue; + if (qstr->len != len) + continue; + if (memcmp(qstr->name, name, len)) + continue; + dget_locked(alias); + spin_unlock(&dcache_lock); + BUG_ON(!d_unhashed(alias)); + return alias; + } + list_add(&entry->d_alias, &inode->i_dentry); +do_negative: + entry->d_inode = inode; + spin_unlock(&dcache_lock); + security_d_instantiate(entry, inode); + return NULL; +} +EXPORT_SYMBOL(d_instantiate_unique); + +/** * d_alloc_root - allocate root dentry * @root_inode: inode to allocate the root for * Index: linux-2.6.10-rc1-up/fs/nfs/dir.c =================================================================== --- linux-2.6.10-rc1-up.orig/fs/nfs/dir.c 2004-10-23 18:02:44.707890967 -0400 +++ linux-2.6.10-rc1-up/fs/nfs/dir.c 2004-10-23 18:02:47.347542726 -0400 @@ -704,6 +704,7 @@ int nfs_is_exclusive_create(struct inode static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd) { + struct dentry *res; struct inode *inode = NULL; int error; struct nfs_fh fhandle; @@ -712,11 +713,11 @@ static struct dentry *nfs_lookup(struct dfprintk(VFS, "NFS: lookup(%s/%s)\n", dentry->d_parent->d_name.name, dentry->d_name.name); - error = -ENAMETOOLONG; + res = ERR_PTR(-ENAMETOOLONG); if (dentry->d_name.len > NFS_SERVER(dir)->namelen) goto out; - error = -ENOMEM; + res = ERR_PTR(-ENOMEM); dentry->d_op = NFS_PROTO(dir)->dentry_ops; lock_kernel(); @@ -730,22 +731,24 @@ static struct dentry *nfs_lookup(struct error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, &fhandle, &fattr); if (error == -ENOENT) goto no_entry; - if (error != 0) + if (error < 0) { + res = ERR_PTR(error); goto out_unlock; - error = -EACCES; + } + res = ERR_PTR(-EACCES); inode = nfs_fhget(dentry->d_sb, &fhandle, &fattr); if (!inode) goto out_unlock; no_entry: - error = 0; - d_add(dentry, inode); + res = d_add_unique(dentry, inode); + if (res != NULL) + dentry = res; nfs_renew_times(dentry); nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); out_unlock: unlock_kernel(); out: - BUG_ON(error > 0); - return ERR_PTR(error); + return res; } #ifdef CONFIG_NFS_V4 @@ -775,15 +778,15 @@ static int is_atomic_open(struct inode * static struct dentry *nfs_atomic_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) { + struct dentry *res = NULL; struct inode *inode = NULL; - int error = 0; /* Check that we are indeed trying to open this file */ if (!is_atomic_open(dir, nd)) goto no_open; if (dentry->d_name.len > NFS_SERVER(dir)->namelen) { - error = -ENAMETOOLONG; + res = ERR_PTR(-ENAMETOOLONG); goto out; } dentry->d_op = NFS_PROTO(dir)->dentry_ops; @@ -805,7 +808,7 @@ static struct dentry *nfs_atomic_lookup( inode = nfs4_atomic_open(dir, dentry, nd); unlock_kernel(); if (IS_ERR(inode)) { - error = PTR_ERR(inode); + int error = PTR_ERR(inode); switch (error) { /* Make a negative dentry */ case -ENOENT: @@ -818,16 +821,18 @@ static struct dentry *nfs_atomic_lookup( /* case -EISDIR: */ /* case -EINVAL: */ default: + res = ERR_PTR(error); goto out; } } no_entry: - d_add(dentry, inode); + res = d_add_unique(dentry, inode); + if (res != NULL) + dentry = res; nfs_renew_times(dentry); nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); out: - BUG_ON(error > 0); - return ERR_PTR(error); + return res; no_open: return nfs_lookup(dir, dentry, nd); } @@ -888,7 +893,7 @@ static struct dentry *nfs_readdir_lookup struct dentry *parent = desc->file->f_dentry; struct inode *dir = parent->d_inode; struct nfs_entry *entry = desc->entry; - struct dentry *dentry; + struct dentry *dentry, *alias; struct qstr name = { .name = entry->name, .len = entry->len, @@ -920,7 +925,11 @@ static struct dentry *nfs_readdir_lookup dput(dentry); return NULL; } - d_add(dentry, inode); + alias = d_add_unique(dentry, inode); + if (alias != NULL) { + dput(dentry); + dentry = alias; + } nfs_renew_times(dentry); nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); return dentry;