

Patch from Hugh Dickins <hugh@veritas.com>

When prune_icache coincides with unmounting, invalidate_inodes notices
the inode it's working on as busy but doesn't wait: Self-destruct in 5
seconds message, and later iput oopses on freed super_block.

Neither end is a fast path, so the patch just adds iprune_sem for exclusion.

The semaphore is held across dispose_list so that
dispose_list->clear_inode->destroy_inode cannot reference a destroyed
superblock.




 inode.c |   16 ++++++++++++++++
 1 files changed, 16 insertions(+)

diff -puN fs/inode.c~hugh-inode-pruning-race-fix fs/inode.c
--- 25/fs/inode.c~hugh-inode-pruning-race-fix	2003-02-20 16:11:15.000000000 -0800
+++ 25-akpm/fs/inode.c	2003-02-20 16:23:02.000000000 -0800
@@ -81,6 +81,16 @@ static LIST_HEAD(anon_hash_chain); /* fo
 spinlock_t inode_lock = SPIN_LOCK_UNLOCKED;
 
 /*
+ * iprune_sem provides exclusion between the kswapd or try_to_free_pages
+ * icache shrinking path, and the umount path.  Without this exclusion,
+ * by the time prune_icache calls iput for the inode whose pages it has
+ * been invalidating, or by the time it calls clear_inode & destroy_inode
+ * from its final dispose_list, the struct super_block they refer to
+ * (for inode->i_sb->s_op) may already have been freed and reused.
+ */
+static DECLARE_MUTEX(iprune_sem);
+
+/*
  * Statistics gathering..
  */
 struct inodes_stat_t inodes_stat;
@@ -320,6 +330,7 @@ int invalidate_inodes(struct super_block
 	int busy;
 	LIST_HEAD(throw_away);
 
+	down(&iprune_sem);
 	spin_lock(&inode_lock);
 	busy = invalidate_list(&inode_in_use, sb, &throw_away);
 	busy |= invalidate_list(&inode_unused, sb, &throw_away);
@@ -328,6 +339,7 @@ int invalidate_inodes(struct super_block
 	spin_unlock(&inode_lock);
 
 	dispose_list(&throw_away);
+	up(&iprune_sem);
 
 	return busy;
 }
@@ -395,6 +407,7 @@ static void prune_icache(int nr_to_scan)
 	int nr_scanned;
 	unsigned long reap = 0;
 
+	down(&iprune_sem);
 	spin_lock(&inode_lock);
 	for (nr_scanned = 0; nr_scanned < nr_to_scan; nr_scanned++) {
 		struct inode *inode;
@@ -429,7 +442,10 @@ static void prune_icache(int nr_to_scan)
 	}
 	inodes_stat.nr_unused -= nr_pruned;
 	spin_unlock(&inode_lock);
+
 	dispose_list(&freeable);
+	up(&iprune_sem);
+
 	if (current_is_kswapd)
 		mod_page_state(kswapd_inodesteal, reap);
 	else

_
