Commit 9eaef27b authored by Trond Myklebust's avatar Trond Myklebust Committed by Linus Torvalds

[PATCH] VFS: Make d_materialise_unique() enforce directory uniqueness

If the caller tries to instantiate a directory using an inode that already
has a dentry alias, then we attempt to rename the existing dentry instead
of instantiating a new one.  Fail with an ELOOP error if the rename would
affect one of our parent directories.

This behaviour is needed in order to avoid issues such as

  http://bugzilla.kernel.org/show_bug.cgi?id=7178Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
Cc: Miklos Szeredi <miklos@szeredi.hu>
Cc: Maneesh Soni <maneesh@in.ibm.com>
Cc: Dipankar Sarma <dipankar@in.ibm.com>
Cc: Neil Brown <neilb@cse.unsw.edu.au>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 3f7705ea
...@@ -1469,23 +1469,21 @@ static void switch_names(struct dentry *dentry, struct dentry *target) ...@@ -1469,23 +1469,21 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
* deleted it. * deleted it.
*/ */
/** /*
* d_move - move a dentry * d_move_locked - move a dentry
* @dentry: entry to move * @dentry: entry to move
* @target: new dentry * @target: new dentry
* *
* Update the dcache to reflect the move of a file name. Negative * Update the dcache to reflect the move of a file name. Negative
* dcache entries should not be moved in this way. * dcache entries should not be moved in this way.
*/ */
static void d_move_locked(struct dentry * dentry, struct dentry * target)
void d_move(struct dentry * dentry, struct dentry * target)
{ {
struct hlist_head *list; struct hlist_head *list;
if (!dentry->d_inode) if (!dentry->d_inode)
printk(KERN_WARNING "VFS: moving negative dcache entry\n"); printk(KERN_WARNING "VFS: moving negative dcache entry\n");
spin_lock(&dcache_lock);
write_seqlock(&rename_lock); write_seqlock(&rename_lock);
/* /*
* XXXX: do we really need to take target->d_lock? * XXXX: do we really need to take target->d_lock?
...@@ -1536,9 +1534,83 @@ void d_move(struct dentry * dentry, struct dentry * target) ...@@ -1536,9 +1534,83 @@ void d_move(struct dentry * dentry, struct dentry * target)
fsnotify_d_move(dentry); fsnotify_d_move(dentry);
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
write_sequnlock(&rename_lock); write_sequnlock(&rename_lock);
}
/**
* d_move - move a dentry
* @dentry: entry to move
* @target: new dentry
*
* Update the dcache to reflect the move of a file name. Negative
* dcache entries should not be moved in this way.
*/
void d_move(struct dentry * dentry, struct dentry * target)
{
spin_lock(&dcache_lock);
d_move_locked(dentry, target);
spin_unlock(&dcache_lock); spin_unlock(&dcache_lock);
} }
/*
* Helper that returns 1 if p1 is a parent of p2, else 0
*/
static int d_isparent(struct dentry *p1, struct dentry *p2)
{
struct dentry *p;
for (p = p2; p->d_parent != p; p = p->d_parent) {
if (p->d_parent == p1)
return 1;
}
return 0;
}
/*
* This helper attempts to cope with remotely renamed directories
*
* It assumes that the caller is already holding
* dentry->d_parent->d_inode->i_mutex and the dcache_lock
*
* Note: If ever the locking in lock_rename() changes, then please
* remember to update this too...
*
* On return, dcache_lock will have been unlocked.
*/
static struct dentry *__d_unalias(struct dentry *dentry, struct dentry *alias)
{
struct mutex *m1 = NULL, *m2 = NULL;
struct dentry *ret;
/* If alias and dentry share a parent, then no extra locks required */
if (alias->d_parent == dentry->d_parent)
goto out_unalias;
/* Check for loops */
ret = ERR_PTR(-ELOOP);
if (d_isparent(alias, dentry))
goto out_err;
/* See lock_rename() */
ret = ERR_PTR(-EBUSY);
if (!mutex_trylock(&dentry->d_sb->s_vfs_rename_mutex))
goto out_err;
m1 = &dentry->d_sb->s_vfs_rename_mutex;
if (!mutex_trylock(&alias->d_parent->d_inode->i_mutex))
goto out_err;
m2 = &alias->d_parent->d_inode->i_mutex;
out_unalias:
d_move_locked(alias, dentry);
ret = alias;
out_err:
spin_unlock(&dcache_lock);
if (m2)
mutex_unlock(m2);
if (m1)
mutex_unlock(m1);
return ret;
}
/* /*
* Prepare an anonymous dentry for life in the superblock's dentry tree as a * Prepare an anonymous dentry for life in the superblock's dentry tree as a
* named dentry in place of the dentry to be replaced. * named dentry in place of the dentry to be replaced.
...@@ -1581,7 +1653,7 @@ static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon) ...@@ -1581,7 +1653,7 @@ static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon)
*/ */
struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode) struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
{ {
struct dentry *alias, *actual; struct dentry *actual;
BUG_ON(!d_unhashed(dentry)); BUG_ON(!d_unhashed(dentry));
...@@ -1593,26 +1665,27 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode) ...@@ -1593,26 +1665,27 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
goto found_lock; goto found_lock;
} }
/* See if a disconnected directory already exists as an anonymous root if (S_ISDIR(inode->i_mode)) {
* that we should splice into the tree instead */ struct dentry *alias;
if (S_ISDIR(inode->i_mode) && (alias = __d_find_alias(inode, 1))) {
spin_lock(&alias->d_lock); /* Does an aliased dentry already exist? */
alias = __d_find_alias(inode, 0);
/* Is this a mountpoint that we could splice into our tree? */ if (alias) {
if (IS_ROOT(alias)) actual = alias;
goto connect_mountpoint; /* Is this an anonymous mountpoint that we could splice
* into our tree? */
if (alias->d_name.len == dentry->d_name.len && if (IS_ROOT(alias)) {
alias->d_parent == dentry->d_parent && spin_lock(&alias->d_lock);
memcmp(alias->d_name.name, __d_materialise_dentry(dentry, alias);
dentry->d_name.name, __d_drop(alias);
dentry->d_name.len) == 0) goto found;
goto replace_with_alias; }
/* Nope, but we must(!) avoid directory aliasing */
spin_unlock(&alias->d_lock); actual = __d_unalias(dentry, alias);
if (IS_ERR(actual))
/* Doh! Seem to be aliasing directories for some reason... */ dput(alias);
dput(alias); goto out_nolock;
}
} }
/* Add a unique reference */ /* Add a unique reference */
...@@ -1628,7 +1701,7 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode) ...@@ -1628,7 +1701,7 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
_d_rehash(actual); _d_rehash(actual);
spin_unlock(&actual->d_lock); spin_unlock(&actual->d_lock);
spin_unlock(&dcache_lock); spin_unlock(&dcache_lock);
out_nolock:
if (actual == dentry) { if (actual == dentry) {
security_d_instantiate(dentry, inode); security_d_instantiate(dentry, inode);
return NULL; return NULL;
...@@ -1637,16 +1710,6 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode) ...@@ -1637,16 +1710,6 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
iput(inode); iput(inode);
return actual; return actual;
/* Convert the anonymous/root alias into an ordinary dentry */
connect_mountpoint:
__d_materialise_dentry(dentry, alias);
/* Replace the candidate dentry with the alias in the tree */
replace_with_alias:
__d_drop(alias);
actual = alias;
goto found;
shouldnt_be_hashed: shouldnt_be_hashed:
spin_unlock(&dcache_lock); spin_unlock(&dcache_lock);
BUG(); BUG();
......
...@@ -935,8 +935,11 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru ...@@ -935,8 +935,11 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru
no_entry: no_entry:
res = d_materialise_unique(dentry, inode); res = d_materialise_unique(dentry, inode);
if (res != NULL) if (res != NULL) {
if (IS_ERR(res))
goto out_unlock;
dentry = res; 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:
...@@ -1132,6 +1135,8 @@ static struct dentry *nfs_readdir_lookup(nfs_readdir_descriptor_t *desc) ...@@ -1132,6 +1135,8 @@ static struct dentry *nfs_readdir_lookup(nfs_readdir_descriptor_t *desc)
alias = d_materialise_unique(dentry, inode); alias = d_materialise_unique(dentry, inode);
if (alias != NULL) { if (alias != NULL) {
dput(dentry); dput(dentry);
if (IS_ERR(alias))
return NULL;
dentry = alias; dentry = alias;
} }
......
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