diff options
author | Trond Myklebust <trond.myklebust@fys.uio.no> | 2005-01-04 21:41:37 +0100 |
---|---|---|
committer | Trond Myklebust <trond.myklebust@fys.uio.no> | 2005-01-04 21:41:37 +0100 |
commit | 89a45174b6b32596ea98fa3f89a243e2c1188a01 (patch) | |
tree | 636d994f758f7ac2cdf51e2b695657b981b2e725 /fs | |
parent | e46e9c893582e0ef43e7e7b187d548252648dd5c (diff) | |
download | history-89a45174b6b32596ea98fa3f89a243e2c1188a01.tar.gz |
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 <trond.myklebust@fys.uio.no>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/dcache.c | 48 | ||||
-rw-r--r-- | fs/nfs/dir.c | 41 |
2 files changed, 73 insertions, 16 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 500c4c4d02f49d..bd8f593a3a7dc8 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -784,6 +784,54 @@ void d_instantiate(struct dentry *entry, struct inode * inode) } /** + * 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 * diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 55e50396dd8149..7dd465690c5fe5 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -704,6 +704,7 @@ int nfs_is_exclusive_create(struct inode *dir, struct nameidata *nd) 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 inode *dir, struct dentry * dentry, stru 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 inode *dir, struct dentry * dentry, stru 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 *dir, struct nameidata *nd) 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(struct inode *dir, struct dentry *dentry 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(struct inode *dir, struct dentry *dentry /* 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(nfs_readdir_descriptor_t *desc) 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(nfs_readdir_descriptor_t *desc) 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; |