diff options
| author | Dmitry Torokhov <dtor@insightbb.com> | 2006-11-04 22:42:39 -0500 | 
|---|---|---|
| committer | Dmitry Torokhov <dtor@insightbb.com> | 2006-11-04 22:42:39 -0500 | 
| commit | 752c58a471c108d64da1676b2925dfbd83eb177e (patch) | |
| tree | fbffa0d7c54cd812950dffc16d642c9d449f4faf /fs/dcache.c | |
| parent | e52b29c2a637f6854d71a45646d7283d984a6dad (diff) | |
| parent | 10b1fbdb0a0ca91847a534ad26d0bc250c25b74f (diff) | |
Merge rsync://rsync.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
Diffstat (limited to 'fs/dcache.c')
| -rw-r--r-- | fs/dcache.c | 282 | 
1 files changed, 241 insertions, 41 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 2355bddad8d..fd4a428998e 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -478,11 +478,12 @@ static void prune_dcache(int count, struct super_block *sb)  			up_read(s_umount);  		}  		spin_unlock(&dentry->d_lock); -		/* Cannot remove the first dentry, and it isn't appropriate -		 * to move it to the head of the list, so give up, and try -		 * later +		/* +		 * Insert dentry at the head of the list as inserting at the +		 * tail leads to a cycle.  		 */ -		break; + 		list_add(&dentry->d_lru, &dentry_unused); +		dentry_stat.nr_unused++;  	}  	spin_unlock(&dcache_lock);  } @@ -549,6 +550,142 @@ repeat:  }  /* + * destroy a single subtree of dentries for unmount + * - see the comments on shrink_dcache_for_umount() for a description of the + *   locking + */ +static void shrink_dcache_for_umount_subtree(struct dentry *dentry) +{ +	struct dentry *parent; +	unsigned detached = 0; + +	BUG_ON(!IS_ROOT(dentry)); + +	/* detach this root from the system */ +	spin_lock(&dcache_lock); +	if (!list_empty(&dentry->d_lru)) { +		dentry_stat.nr_unused--; +		list_del_init(&dentry->d_lru); +	} +	__d_drop(dentry); +	spin_unlock(&dcache_lock); + +	for (;;) { +		/* descend to the first leaf in the current subtree */ +		while (!list_empty(&dentry->d_subdirs)) { +			struct dentry *loop; + +			/* this is a branch with children - detach all of them +			 * from the system in one go */ +			spin_lock(&dcache_lock); +			list_for_each_entry(loop, &dentry->d_subdirs, +					    d_u.d_child) { +				if (!list_empty(&loop->d_lru)) { +					dentry_stat.nr_unused--; +					list_del_init(&loop->d_lru); +				} + +				__d_drop(loop); +				cond_resched_lock(&dcache_lock); +			} +			spin_unlock(&dcache_lock); + +			/* move to the first child */ +			dentry = list_entry(dentry->d_subdirs.next, +					    struct dentry, d_u.d_child); +		} + +		/* consume the dentries from this leaf up through its parents +		 * until we find one with children or run out altogether */ +		do { +			struct inode *inode; + +			if (atomic_read(&dentry->d_count) != 0) { +				printk(KERN_ERR +				       "BUG: Dentry %p{i=%lx,n=%s}" +				       " still in use (%d)" +				       " [unmount of %s %s]\n", +				       dentry, +				       dentry->d_inode ? +				       dentry->d_inode->i_ino : 0UL, +				       dentry->d_name.name, +				       atomic_read(&dentry->d_count), +				       dentry->d_sb->s_type->name, +				       dentry->d_sb->s_id); +				BUG(); +			} + +			parent = dentry->d_parent; +			if (parent == dentry) +				parent = NULL; +			else +				atomic_dec(&parent->d_count); + +			list_del(&dentry->d_u.d_child); +			detached++; + +			inode = dentry->d_inode; +			if (inode) { +				dentry->d_inode = NULL; +				list_del_init(&dentry->d_alias); +				if (dentry->d_op && dentry->d_op->d_iput) +					dentry->d_op->d_iput(dentry, inode); +				else +					iput(inode); +			} + +			d_free(dentry); + +			/* finished when we fall off the top of the tree, +			 * otherwise we ascend to the parent and move to the +			 * next sibling if there is one */ +			if (!parent) +				goto out; + +			dentry = parent; + +		} while (list_empty(&dentry->d_subdirs)); + +		dentry = list_entry(dentry->d_subdirs.next, +				    struct dentry, d_u.d_child); +	} +out: +	/* several dentries were freed, need to correct nr_dentry */ +	spin_lock(&dcache_lock); +	dentry_stat.nr_dentry -= detached; +	spin_unlock(&dcache_lock); +} + +/* + * destroy the dentries attached to a superblock on unmounting + * - we don't need to use dentry->d_lock, and only need dcache_lock when + *   removing the dentry from the system lists and hashes because: + *   - the superblock is detached from all mountings and open files, so the + *     dentry trees will not be rearranged by the VFS + *   - s_umount is write-locked, so the memory pressure shrinker will ignore + *     any dentries belonging to this superblock that it comes across + *   - the filesystem itself is no longer permitted to rearrange the dentries + *     in this superblock + */ +void shrink_dcache_for_umount(struct super_block *sb) +{ +	struct dentry *dentry; + +	if (down_read_trylock(&sb->s_umount)) +		BUG(); + +	dentry = sb->s_root; +	sb->s_root = NULL; +	atomic_dec(&dentry->d_count); +	shrink_dcache_for_umount_subtree(dentry); + +	while (!hlist_empty(&sb->s_anon)) { +		dentry = hlist_entry(sb->s_anon.first, struct dentry, d_hash); +		shrink_dcache_for_umount_subtree(dentry); +	} +} + +/*   * 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. @@ -1339,23 +1476,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? @@ -1406,7 +1541,81 @@ already_unhashed:  	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;  }  /* @@ -1451,7 +1660,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)); @@ -1463,26 +1672,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); - -		/* 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); +	if (S_ISDIR(inode->i_mode)) { +		struct dentry *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 */ @@ -1498,7 +1708,7 @@ found:  	_d_rehash(actual);  	spin_unlock(&actual->d_lock);  	spin_unlock(&dcache_lock); - +out_nolock:  	if (actual == dentry) {  		security_d_instantiate(dentry, inode);  		return NULL; @@ -1507,16 +1717,6 @@ found:  	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();  | 
