Commit 3d48f700 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] Fix writeback_inodes-vs-umount race

Fix bug identified by Chris Mason.

If writeback_inodes is left holding a ref on the superblock's last inode then
the superblock list walk can race with umount and the superblock can be
released.

Take and put a ref against the superblock to fix that.
parent 066479e3
...@@ -397,12 +397,16 @@ writeback_inodes(struct writeback_control *wbc) ...@@ -397,12 +397,16 @@ writeback_inodes(struct writeback_control *wbc)
spin_lock(&inode_lock); spin_lock(&inode_lock);
spin_lock(&sb_lock); spin_lock(&sb_lock);
restart:
sb = sb_entry(super_blocks.prev); sb = sb_entry(super_blocks.prev);
for (; sb != sb_entry(&super_blocks); sb = sb_entry(sb->s_list.prev)) { for (; sb != sb_entry(&super_blocks); sb = sb_entry(sb->s_list.prev)) {
if (!list_empty(&sb->s_dirty) || !list_empty(&sb->s_io)) { if (!list_empty(&sb->s_dirty) || !list_empty(&sb->s_io)) {
sb->s_count++;
spin_unlock(&sb_lock); spin_unlock(&sb_lock);
sync_sb_inodes(sb, wbc); sync_sb_inodes(sb, wbc);
spin_lock(&sb_lock); spin_lock(&sb_lock);
if (__put_super(sb))
goto restart;
} }
if (wbc->nr_to_write <= 0) if (wbc->nr_to_write <= 0)
break; break;
......
...@@ -101,6 +101,21 @@ static inline void destroy_super(struct super_block *s) ...@@ -101,6 +101,21 @@ static inline void destroy_super(struct super_block *s)
/* Superblock refcounting */ /* Superblock refcounting */
/*
* Drop a superblock's refcount. Returns non-zero if the superblock was
* destroyed. The caller must hold sb_lock.
*/
int __put_super(struct super_block *sb)
{
int ret = 0;
if (!--sb->s_count) {
destroy_super(sb);
ret = 1;
}
return ret;
}
/** /**
* put_super - drop a temporary reference to superblock * put_super - drop a temporary reference to superblock
* @s: superblock in question * @s: superblock in question
...@@ -108,14 +123,14 @@ static inline void destroy_super(struct super_block *s) ...@@ -108,14 +123,14 @@ static inline void destroy_super(struct super_block *s)
* Drops a temporary reference, frees superblock if there's no * Drops a temporary reference, frees superblock if there's no
* references left. * references left.
*/ */
static inline void put_super(struct super_block *s) static void put_super(struct super_block *sb)
{ {
spin_lock(&sb_lock); spin_lock(&sb_lock);
if (!--s->s_count) __put_super(sb);
destroy_super(s);
spin_unlock(&sb_lock); spin_unlock(&sb_lock);
} }
/** /**
* deactivate_super - drop an active reference to superblock * deactivate_super - drop an active reference to superblock
* @s: superblock to deactivate * @s: superblock to deactivate
......
...@@ -1114,6 +1114,7 @@ struct super_block *sget(struct file_system_type *type, ...@@ -1114,6 +1114,7 @@ struct super_block *sget(struct file_system_type *type,
void *data); void *data);
struct super_block *get_sb_pseudo(struct file_system_type *, char *, struct super_block *get_sb_pseudo(struct file_system_type *, char *,
struct super_operations *ops, unsigned long); struct super_operations *ops, unsigned long);
int __put_super(struct super_block *sb);
void unnamed_dev_init(void); void unnamed_dev_init(void);
/* Alas, no aliases. Too much hassle with bringing module.h everywhere */ /* Alas, no aliases. Too much hassle with bringing module.h everywhere */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment