summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--fs/inode.c46
-rw-r--r--fs/internal.h1
-rw-r--r--fs/notify/inode_mark.c2
-rw-r--r--fs/super.c8
4 files changed, 51 insertions, 6 deletions
diff --git a/fs/inode.c b/fs/inode.c
index f6b3b52f127..a6d60682f0f 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -478,6 +478,49 @@ static void dispose_list(struct list_head *head)
}
/**
+ * evict_inodes - evict all evictable inodes for a superblock
+ * @sb: superblock to operate on
+ *
+ * Make sure that no inodes with zero refcount are retained. This is
+ * called by superblock shutdown after having MS_ACTIVE flag removed,
+ * so any inode reaching zero refcount during or after that call will
+ * be immediately evicted.
+ */
+void evict_inodes(struct super_block *sb)
+{
+ struct inode *inode, *next;
+ LIST_HEAD(dispose);
+
+ down_write(&iprune_sem);
+
+ spin_lock(&inode_lock);
+ list_for_each_entry_safe(inode, next, &sb->s_inodes, i_sb_list) {
+ if (atomic_read(&inode->i_count))
+ continue;
+
+ if (inode->i_state & (I_NEW | I_FREEING | I_WILL_FREE)) {
+ WARN_ON(1);
+ continue;
+ }
+
+ inode->i_state |= I_FREEING;
+
+ /*
+ * Move the inode off the IO lists and LRU once I_FREEING is
+ * set so that it won't get moved back on there if it is dirty.
+ */
+ list_move(&inode->i_lru, &dispose);
+ list_del_init(&inode->i_wb_list);
+ if (!(inode->i_state & (I_DIRTY | I_SYNC)))
+ percpu_counter_dec(&nr_inodes_unused);
+ }
+ spin_unlock(&inode_lock);
+
+ dispose_list(&dispose);
+ up_write(&iprune_sem);
+}
+
+/**
* invalidate_inodes - attempt to free all inodes on a superblock
* @sb: superblock to operate on
*
@@ -493,9 +536,8 @@ int invalidate_inodes(struct super_block *sb)
down_write(&iprune_sem);
spin_lock(&inode_lock);
- fsnotify_unmount_inodes(&sb->s_inodes);
list_for_each_entry_safe(inode, next, &sb->s_inodes, i_sb_list) {
- if (inode->i_state & I_NEW)
+ if (inode->i_state & (I_NEW | I_FREEING | I_WILL_FREE))
continue;
if (atomic_read(&inode->i_count)) {
busy = 1;
diff --git a/fs/internal.h b/fs/internal.h
index 4cc67eb6ed5..ebad3b90752 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -106,4 +106,5 @@ extern void release_open_intent(struct nameidata *);
* inode.c
*/
extern int get_nr_dirty_inodes(void);
+extern int evict_inodes(struct super_block *);
extern int invalidate_inodes(struct super_block *);
diff --git a/fs/notify/inode_mark.c b/fs/notify/inode_mark.c
index 33297c00506..21ed10660b8 100644
--- a/fs/notify/inode_mark.c
+++ b/fs/notify/inode_mark.c
@@ -240,6 +240,7 @@ void fsnotify_unmount_inodes(struct list_head *list)
{
struct inode *inode, *next_i, *need_iput = NULL;
+ spin_lock(&inode_lock);
list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
struct inode *need_iput_tmp;
@@ -297,4 +298,5 @@ void fsnotify_unmount_inodes(struct list_head *list)
spin_lock(&inode_lock);
}
+ spin_unlock(&inode_lock);
}
diff --git a/fs/super.c b/fs/super.c
index 8819e3a7ff2..b9c9869165d 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -273,14 +273,14 @@ void generic_shutdown_super(struct super_block *sb)
get_fs_excl();
sb->s_flags &= ~MS_ACTIVE;
- /* bad name - it should be evict_inodes() */
- invalidate_inodes(sb);
+ fsnotify_unmount_inodes(&sb->s_inodes);
+
+ evict_inodes(sb);
if (sop->put_super)
sop->put_super(sb);
- /* Forget any remaining inodes */
- if (invalidate_inodes(sb)) {
+ if (!list_empty(&sb->s_inodes)) {
printk("VFS: Busy inodes after unmount of %s. "
"Self-destruct in 5 seconds. Have a nice day...\n",
sb->s_id);