[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=7178

Signed-off-by: Trond 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: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
Trond Myklebust 2006-10-21 10:24:20 -07:00 committed by Linus Torvalds
parent 3f7705eab6
commit 9eaef27b36
2 changed files with 105 additions and 37 deletions

View file

@ -1469,23 +1469,21 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
* deleted it.
*/
/**
* d_move - move a dentry
/*
* d_move_locked - 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)
static void d_move_locked(struct dentry * dentry, struct dentry * target)
{
struct hlist_head *list;
if (!dentry->d_inode)
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
spin_lock(&dcache_lock);
write_seqlock(&rename_lock);
/*
* XXXX: do we really need to take target->d_lock?
@ -1536,9 +1534,83 @@ void d_move(struct dentry * dentry, struct dentry * target)
fsnotify_d_move(dentry);
spin_unlock(&dentry->d_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);
}
/*
* 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
* 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)
*/
struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
{
struct dentry *alias, *actual;
struct dentry *actual;
BUG_ON(!d_unhashed(dentry));
@ -1593,26 +1665,27 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
goto found_lock;
}
/* See if a disconnected directory already exists as an anonymous root
* that we should splice into the tree instead */
if (S_ISDIR(inode->i_mode) && (alias = __d_find_alias(inode, 1))) {
spin_lock(&alias->d_lock);
if (S_ISDIR(inode->i_mode)) {
struct dentry *alias;
/* Is this a mountpoint that we could splice into our tree? */
if (IS_ROOT(alias))
goto connect_mountpoint;
if (alias->d_name.len == dentry->d_name.len &&
alias->d_parent == dentry->d_parent &&
memcmp(alias->d_name.name,
dentry->d_name.name,
dentry->d_name.len) == 0)
goto replace_with_alias;
spin_unlock(&alias->d_lock);
/* Doh! Seem to be aliasing directories for some reason... */
dput(alias);
/* Does an aliased dentry already exist? */
alias = __d_find_alias(inode, 0);
if (alias) {
actual = alias;
/* Is this an anonymous mountpoint that we could splice
* into our tree? */
if (IS_ROOT(alias)) {
spin_lock(&alias->d_lock);
__d_materialise_dentry(dentry, alias);
__d_drop(alias);
goto found;
}
/* Nope, but we must(!) avoid directory aliasing */
actual = __d_unalias(dentry, alias);
if (IS_ERR(actual))
dput(alias);
goto out_nolock;
}
}
/* Add a unique reference */
@ -1628,7 +1701,7 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
_d_rehash(actual);
spin_unlock(&actual->d_lock);
spin_unlock(&dcache_lock);
out_nolock:
if (actual == dentry) {
security_d_instantiate(dentry, inode);
return NULL;
@ -1637,16 +1710,6 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
iput(inode);
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:
spin_unlock(&dcache_lock);
BUG();

View file

@ -935,8 +935,11 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru
no_entry:
res = d_materialise_unique(dentry, inode);
if (res != NULL)
if (res != NULL) {
if (IS_ERR(res))
goto out_unlock;
dentry = res;
}
nfs_renew_times(dentry);
nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
out_unlock:
@ -1132,6 +1135,8 @@ static struct dentry *nfs_readdir_lookup(nfs_readdir_descriptor_t *desc)
alias = d_materialise_unique(dentry, inode);
if (alias != NULL) {
dput(dentry);
if (IS_ERR(alias))
return NULL;
dentry = alias;
}