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) ...@@ -783,6 +783,54 @@ void d_instantiate(struct dentry *entry, struct inode * inode)
security_d_instantiate(entry, 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 * d_alloc_root - allocate root dentry
* @root_inode: inode to allocate the root for * @root_inode: inode to allocate the root for
......
...@@ -704,6 +704,7 @@ int nfs_is_exclusive_create(struct inode *dir, struct nameidata *nd) ...@@ -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) static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd)
{ {
struct dentry *res;
struct inode *inode = NULL; struct inode *inode = NULL;
int error; int error;
struct nfs_fh fhandle; struct nfs_fh fhandle;
...@@ -712,11 +713,11 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru ...@@ -712,11 +713,11 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru
dfprintk(VFS, "NFS: lookup(%s/%s)\n", dfprintk(VFS, "NFS: lookup(%s/%s)\n",
dentry->d_parent->d_name.name, dentry->d_name.name); 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) if (dentry->d_name.len > NFS_SERVER(dir)->namelen)
goto out; goto out;
error = -ENOMEM; res = ERR_PTR(-ENOMEM);
dentry->d_op = NFS_PROTO(dir)->dentry_ops; dentry->d_op = NFS_PROTO(dir)->dentry_ops;
lock_kernel(); lock_kernel();
...@@ -730,22 +731,24 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru ...@@ -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); error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, &fhandle, &fattr);
if (error == -ENOENT) if (error == -ENOENT)
goto no_entry; goto no_entry;
if (error != 0) if (error < 0) {
res = ERR_PTR(error);
goto out_unlock; goto out_unlock;
error = -EACCES; }
res = ERR_PTR(-EACCES);
inode = nfs_fhget(dentry->d_sb, &fhandle, &fattr); inode = nfs_fhget(dentry->d_sb, &fhandle, &fattr);
if (!inode) if (!inode)
goto out_unlock; goto out_unlock;
no_entry: no_entry:
error = 0; res = d_add_unique(dentry, inode);
d_add(dentry, inode); if (res != NULL)
dentry = res;
nfs_renew_times(dentry); nfs_renew_times(dentry);
nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
out_unlock: out_unlock:
unlock_kernel(); unlock_kernel();
out: out:
BUG_ON(error > 0); return res;
return ERR_PTR(error);
} }
#ifdef CONFIG_NFS_V4 #ifdef CONFIG_NFS_V4
...@@ -775,15 +778,15 @@ static int is_atomic_open(struct inode *dir, struct nameidata *nd) ...@@ -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) static struct dentry *nfs_atomic_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{ {
struct dentry *res = NULL;
struct inode *inode = NULL; struct inode *inode = NULL;
int error = 0;
/* Check that we are indeed trying to open this file */ /* Check that we are indeed trying to open this file */
if (!is_atomic_open(dir, nd)) if (!is_atomic_open(dir, nd))
goto no_open; goto no_open;
if (dentry->d_name.len > NFS_SERVER(dir)->namelen) { if (dentry->d_name.len > NFS_SERVER(dir)->namelen) {
error = -ENAMETOOLONG; res = ERR_PTR(-ENAMETOOLONG);
goto out; goto out;
} }
dentry->d_op = NFS_PROTO(dir)->dentry_ops; dentry->d_op = NFS_PROTO(dir)->dentry_ops;
...@@ -805,7 +808,7 @@ static struct dentry *nfs_atomic_lookup(struct inode *dir, struct dentry *dentry ...@@ -805,7 +808,7 @@ static struct dentry *nfs_atomic_lookup(struct inode *dir, struct dentry *dentry
inode = nfs4_atomic_open(dir, dentry, nd); inode = nfs4_atomic_open(dir, dentry, nd);
unlock_kernel(); unlock_kernel();
if (IS_ERR(inode)) { if (IS_ERR(inode)) {
error = PTR_ERR(inode); int error = PTR_ERR(inode);
switch (error) { switch (error) {
/* Make a negative dentry */ /* Make a negative dentry */
case -ENOENT: case -ENOENT:
...@@ -818,16 +821,18 @@ static struct dentry *nfs_atomic_lookup(struct inode *dir, struct dentry *dentry ...@@ -818,16 +821,18 @@ static struct dentry *nfs_atomic_lookup(struct inode *dir, struct dentry *dentry
/* case -EISDIR: */ /* case -EISDIR: */
/* case -EINVAL: */ /* case -EINVAL: */
default: default:
res = ERR_PTR(error);
goto out; goto out;
} }
} }
no_entry: no_entry:
d_add(dentry, inode); res = d_add_unique(dentry, inode);
if (res != NULL)
dentry = res;
nfs_renew_times(dentry); nfs_renew_times(dentry);
nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
out: out:
BUG_ON(error > 0); return res;
return ERR_PTR(error);
no_open: no_open:
return nfs_lookup(dir, dentry, nd); return nfs_lookup(dir, dentry, nd);
} }
...@@ -888,7 +893,7 @@ static struct dentry *nfs_readdir_lookup(nfs_readdir_descriptor_t *desc) ...@@ -888,7 +893,7 @@ static struct dentry *nfs_readdir_lookup(nfs_readdir_descriptor_t *desc)
struct dentry *parent = desc->file->f_dentry; struct dentry *parent = desc->file->f_dentry;
struct inode *dir = parent->d_inode; struct inode *dir = parent->d_inode;
struct nfs_entry *entry = desc->entry; struct nfs_entry *entry = desc->entry;
struct dentry *dentry; struct dentry *dentry, *alias;
struct qstr name = { struct qstr name = {
.name = entry->name, .name = entry->name,
.len = entry->len, .len = entry->len,
...@@ -920,7 +925,11 @@ static struct dentry *nfs_readdir_lookup(nfs_readdir_descriptor_t *desc) ...@@ -920,7 +925,11 @@ static struct dentry *nfs_readdir_lookup(nfs_readdir_descriptor_t *desc)
dput(dentry); dput(dentry);
return NULL; return NULL;
} }
d_add(dentry, inode); alias = d_add_unique(dentry, inode);
if (alias != NULL) {
dput(dentry);
dentry = alias;
}
nfs_renew_times(dentry); nfs_renew_times(dentry);
nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
return dentry; return dentry;
......
...@@ -199,6 +199,7 @@ static inline int dname_external(struct dentry *dentry) ...@@ -199,6 +199,7 @@ static inline int dname_external(struct dentry *dentry)
* These are the low-level FS interfaces to the dcache.. * These are the low-level FS interfaces to the dcache..
*/ */
extern void d_instantiate(struct dentry *, struct inode *); extern void d_instantiate(struct dentry *, struct inode *);
extern struct dentry * d_instantiate_unique(struct dentry *, struct inode *);
extern void d_delete(struct dentry *); extern void d_delete(struct dentry *);
/* allocate/de-allocate */ /* allocate/de-allocate */
...@@ -242,6 +243,23 @@ static inline void d_add(struct dentry *entry, struct inode *inode) ...@@ -242,6 +243,23 @@ static inline void d_add(struct dentry *entry, struct inode *inode)
d_rehash(entry); 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 */ /* used for rename() and baskets */
extern void d_move(struct dentry *, struct dentry *); 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