diff options
Diffstat (limited to 'fs/dcache.c')
| -rw-r--r-- | fs/dcache.c | 121 | 
1 files changed, 67 insertions, 54 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 2a6bd9a4ae97..a39fe47c466f 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -296,8 +296,12 @@ static struct dentry *d_kill(struct dentry *dentry, struct dentry *parent)  	__releases(parent->d_lock)  	__releases(dentry->d_inode->i_lock)  { -	dentry->d_parent = NULL;  	list_del(&dentry->d_u.d_child); +	/* +	 * Inform try_to_ascend() that we are no longer attached to the +	 * dentry tree +	 */ +	dentry->d_flags |= DCACHE_DISCONNECTED;  	if (parent)  		spin_unlock(&parent->d_lock);  	dentry_iput(dentry); @@ -1012,6 +1016,35 @@ void shrink_dcache_for_umount(struct super_block *sb)  }  /* + * This tries to ascend one level of parenthood, but + * we can race with renaming, so we need to re-check + * the parenthood after dropping the lock and check + * that the sequence number still matches. + */ +static struct dentry *try_to_ascend(struct dentry *old, int locked, unsigned seq) +{ +	struct dentry *new = old->d_parent; + +	rcu_read_lock(); +	spin_unlock(&old->d_lock); +	spin_lock(&new->d_lock); + +	/* +	 * might go back up the wrong parent if we have had a rename +	 * or deletion +	 */ +	if (new != old->d_parent || +		 (old->d_flags & DCACHE_DISCONNECTED) || +		 (!locked && read_seqretry(&rename_lock, seq))) { +		spin_unlock(&new->d_lock); +		new = NULL; +	} +	rcu_read_unlock(); +	return new; +} + + +/*   * Search for at least 1 mount point in the dentry's subdirs.   * We descend to the next level whenever the d_subdirs   * list is non-empty and continue searching. @@ -1066,24 +1099,10 @@ resume:  	 * All done at this level ... ascend and resume the search.  	 */  	if (this_parent != parent) { -		struct dentry *tmp; -		struct dentry *child; - -		tmp = this_parent->d_parent; -		rcu_read_lock(); -		spin_unlock(&this_parent->d_lock); -		child = this_parent; -		this_parent = tmp; -		spin_lock(&this_parent->d_lock); -		/* might go back up the wrong parent if we have had a rename -		 * or deletion */ -		if (this_parent != child->d_parent || -			 (!locked && read_seqretry(&rename_lock, seq))) { -			spin_unlock(&this_parent->d_lock); -			rcu_read_unlock(); +		struct dentry *child = this_parent; +		this_parent = try_to_ascend(this_parent, locked, seq); +		if (!this_parent)  			goto rename_retry; -		} -		rcu_read_unlock();  		next = child->d_u.d_child.next;  		goto resume;  	} @@ -1181,24 +1200,10 @@ resume:  	 * All done at this level ... ascend and resume the search.  	 */  	if (this_parent != parent) { -		struct dentry *tmp; -		struct dentry *child; - -		tmp = this_parent->d_parent; -		rcu_read_lock(); -		spin_unlock(&this_parent->d_lock); -		child = this_parent; -		this_parent = tmp; -		spin_lock(&this_parent->d_lock); -		/* might go back up the wrong parent if we have had a rename -		 * or deletion */ -		if (this_parent != child->d_parent || -			(!locked && read_seqretry(&rename_lock, seq))) { -			spin_unlock(&this_parent->d_lock); -			rcu_read_unlock(); +		struct dentry *child = this_parent; +		this_parent = try_to_ascend(this_parent, locked, seq); +		if (!this_parent)  			goto rename_retry; -		} -		rcu_read_unlock();  		next = child->d_u.d_child.next;  		goto resume;  	} @@ -1523,6 +1528,28 @@ struct dentry * d_alloc_root(struct inode * root_inode)  }  EXPORT_SYMBOL(d_alloc_root); +static struct dentry * __d_find_any_alias(struct inode *inode) +{ +	struct dentry *alias; + +	if (list_empty(&inode->i_dentry)) +		return NULL; +	alias = list_first_entry(&inode->i_dentry, struct dentry, d_alias); +	__dget(alias); +	return alias; +} + +static struct dentry * d_find_any_alias(struct inode *inode) +{ +	struct dentry *de; + +	spin_lock(&inode->i_lock); +	de = __d_find_any_alias(inode); +	spin_unlock(&inode->i_lock); +	return de; +} + +  /**   * d_obtain_alias - find or allocate a dentry for a given inode   * @inode: inode to allocate the dentry for @@ -1552,7 +1579,7 @@ struct dentry *d_obtain_alias(struct inode *inode)  	if (IS_ERR(inode))  		return ERR_CAST(inode); -	res = d_find_alias(inode); +	res = d_find_any_alias(inode);  	if (res)  		goto out_iput; @@ -1565,7 +1592,7 @@ struct dentry *d_obtain_alias(struct inode *inode)  	spin_lock(&inode->i_lock); -	res = __d_find_alias(inode, 0); +	res = __d_find_any_alias(inode);  	if (res) {  		spin_unlock(&inode->i_lock);  		dput(tmp); @@ -2920,28 +2947,14 @@ resume:  		spin_unlock(&dentry->d_lock);  	}  	if (this_parent != root) { -		struct dentry *tmp; -		struct dentry *child; - -		tmp = this_parent->d_parent; +		struct dentry *child = this_parent;  		if (!(this_parent->d_flags & DCACHE_GENOCIDE)) {  			this_parent->d_flags |= DCACHE_GENOCIDE;  			this_parent->d_count--;  		} -		rcu_read_lock(); -		spin_unlock(&this_parent->d_lock); -		child = this_parent; -		this_parent = tmp; -		spin_lock(&this_parent->d_lock); -		/* might go back up the wrong parent if we have had a rename -		 * or deletion */ -		if (this_parent != child->d_parent || -			 (!locked && read_seqretry(&rename_lock, seq))) { -			spin_unlock(&this_parent->d_lock); -			rcu_read_unlock(); +		this_parent = try_to_ascend(this_parent, locked, seq); +		if (!this_parent)  			goto rename_retry; -		} -		rcu_read_unlock();  		next = child->d_u.d_child.next;  		goto resume;  	}  | 
