[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:
parent
3f7705eab6
commit
9eaef27b36
2 changed files with 105 additions and 37 deletions
135
fs/dcache.c
135
fs/dcache.c
|
@ -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)
|
||||||
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)
|
||||||
*/
|
*/
|
||||||
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)
|
||||||
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);
|
|
||||||
|
|
||||||
/* Is this a mountpoint that we could splice into our tree? */
|
/* Does an aliased dentry already exist? */
|
||||||
if (IS_ROOT(alias))
|
alias = __d_find_alias(inode, 0);
|
||||||
goto connect_mountpoint;
|
if (alias) {
|
||||||
|
actual = alias;
|
||||||
if (alias->d_name.len == dentry->d_name.len &&
|
/* Is this an anonymous mountpoint that we could splice
|
||||||
alias->d_parent == dentry->d_parent &&
|
* into our tree? */
|
||||||
memcmp(alias->d_name.name,
|
if (IS_ROOT(alias)) {
|
||||||
dentry->d_name.name,
|
spin_lock(&alias->d_lock);
|
||||||
dentry->d_name.len) == 0)
|
__d_materialise_dentry(dentry, alias);
|
||||||
goto replace_with_alias;
|
__d_drop(alias);
|
||||||
|
goto found;
|
||||||
spin_unlock(&alias->d_lock);
|
}
|
||||||
|
/* Nope, but we must(!) avoid directory aliasing */
|
||||||
/* Doh! Seem to be aliasing directories for some reason... */
|
actual = __d_unalias(dentry, alias);
|
||||||
dput(alias);
|
if (IS_ERR(actual))
|
||||||
|
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)
|
||||||
_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)
|
||||||
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
|
||||||
|
|
||||||
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)
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue