diff options
Diffstat (limited to 'fs/dcache.c')
| -rw-r--r-- | fs/dcache.c | 134 | 
1 files changed, 76 insertions, 58 deletions
| diff --git a/fs/dcache.c b/fs/dcache.c index 0c6d5c549d8..a39fe47c466 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -176,6 +176,7 @@ static void d_free(struct dentry *dentry)  /**   * dentry_rcuwalk_barrier - invalidate in-progress rcu-walk lookups + * @dentry: the target dentry   * After this call, in-progress rcu-walk path lookup will fail. This   * should be called after unhashing, and after changing d_inode (if   * the dentry has not already been unhashed). @@ -281,6 +282,7 @@ static void dentry_lru_move_tail(struct dentry *dentry)  /**   * d_kill - kill dentry and return parent   * @dentry: dentry to kill + * @parent: parent dentry   *   * The dentry must already be unhashed and removed from the LRU.   * @@ -294,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); @@ -1010,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. @@ -1064,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;  	} @@ -1179,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;  	} @@ -1357,8 +1364,8 @@ EXPORT_SYMBOL(d_alloc_name);  void d_set_d_op(struct dentry *dentry, const struct dentry_operations *op)  { -	BUG_ON(dentry->d_op); -	BUG_ON(dentry->d_flags & (DCACHE_OP_HASH	| +	WARN_ON_ONCE(dentry->d_op); +	WARN_ON_ONCE(dentry->d_flags & (DCACHE_OP_HASH	|  				DCACHE_OP_COMPARE	|  				DCACHE_OP_REVALIDATE	|  				DCACHE_OP_DELETE )); @@ -1380,8 +1387,11 @@ EXPORT_SYMBOL(d_set_d_op);  static void __d_instantiate(struct dentry *dentry, struct inode *inode)  {  	spin_lock(&dentry->d_lock); -	if (inode) +	if (inode) { +		if (unlikely(IS_AUTOMOUNT(inode))) +			dentry->d_flags |= DCACHE_NEED_AUTOMOUNT;  		list_add(&dentry->d_alias, &inode->i_dentry); +	}  	dentry->d_inode = inode;  	dentry_rcuwalk_barrier(dentry);  	spin_unlock(&dentry->d_lock); @@ -1518,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 @@ -1547,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; @@ -1560,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); @@ -1970,7 +2002,7 @@ out:  /**   * d_validate - verify dentry provided from insecure source (deprecated)   * @dentry: The dentry alleged to be valid child of @dparent - * @parent: The parent dentry (known to be valid) + * @dparent: The parent dentry (known to be valid)   *   * An insecure source has sent us a dentry, here we verify it and dget() it.   * This is used by ncpfs in its readdir implementation. @@ -2915,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;  	} | 
