Commit 89a45174 authored by Trond Myklebust's avatar Trond Myklebust

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: default avatarTrond Myklebust <trond.myklebust@fys.uio.no>
parent e46e9c89
......@@ -783,6 +783,54 @@ void d_instantiate(struct dentry *entry, struct inode * inode)
security_d_instantiate(entry, 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
......
......@@ -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;
......
......@@ -199,6 +199,7 @@ static inline int dname_external(struct dentry *dentry)
* 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 *entry, struct inode *inode)
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 *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment