aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorTrond Myklebust <trond.myklebust@fys.uio.no>2005-01-04 21:41:37 +0100
committerTrond Myklebust <trond.myklebust@fys.uio.no>2005-01-04 21:41:37 +0100
commit89a45174b6b32596ea98fa3f89a243e2c1188a01 (patch)
tree636d994f758f7ac2cdf51e2b695657b981b2e725 /fs
parente46e9c893582e0ef43e7e7b187d548252648dd5c (diff)
downloadhistory-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.c48
-rw-r--r--fs/nfs/dir.c41
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;