summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKirill Korotaev <dev@sw.ru>2004-09-22 04:21:31 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2004-09-22 04:21:31 -0700
commit4e56755a7d65add91da1ab50b56ea77cf61bd599 (patch)
tree3d71af42839806673bcedce8cf37f4ea9a38936f
parenta5b51d79817282ee3790271d99914266715fa479 (diff)
[PATCH] Fix of race in writeback_inodes()
This patch fixes race in writeback_inodes() described below: writeback_inodes() { .... sb->s_count++; spin_unlock(&sb_lock); .... spin_lock(&sb_lock); if (__put_super(sb)) <<< X goto restart; } } deactivate_super() { fs->kill_sb(s); kill_block_super(sb) generic_shutdown_super(sb) spin_lock(&sb_lock); list_del(&sb->s_list); <<< Y spin_unlock(&sb_lock); .... put_super(s); spin_lock(&sb_lock); __put_super(sb); <<< Z spin_unlock(&sb_lock); } The problem with it is that writeback_inodes() supposes that if __put_super() returns 0 then no super block was deleted from the list and we can safely traverse sb list further. But as it is obvious from the deactivate_super() it's not actually true. because at point Y we delete super block from the list and drop the lock. We do __put_super() very much later... So we can find sb with poisoned sb->s_list at point X and we won't be the last sb reference holders. The last reference will be dropped in point Z. So in case of the following sequence of execution Y -> X -> Z we'll get an oops after point X in writeback_inodes(). This patch introduces __put_super_and_need_restart() function which allows safe traversing of sb list. I'll send a couple of patches later which remove O(n^2) algos and using this function. Signed-Off-By: Kirill Korotaev <dev@sw.ru> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--fs/fs-writeback.c2
-rw-r--r--fs/super.c26
-rw-r--r--include/linux/fs.h1
3 files changed, 26 insertions, 3 deletions
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index f200e0b33ccc..fe1825d45f5a 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -423,7 +423,7 @@ restart:
up_read(&sb->s_umount);
}
spin_lock(&sb_lock);
- if (__put_super(sb))
+ if (__put_super_and_need_restart(sb))
goto restart;
}
if (wbc->nr_to_write <= 0)
diff --git a/fs/super.c b/fs/super.c
index 38c23bf558d4..6f8960dbef68 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -116,6 +116,27 @@ int __put_super(struct super_block *sb)
return ret;
}
+/*
+ * Drop a superblock's refcount.
+ * Returns non-zero if the superblock is about to be destroyed and
+ * at least is already removed from super_blocks list, so if we are
+ * making a loop through super blocks then we need to restart.
+ * The caller must hold sb_lock.
+ */
+int __put_super_and_need_restart(struct super_block *sb)
+{
+ /* check for race with generic_shutdown_super() */
+ if (list_empty(&sb->s_list)) {
+ /* super block is removed, need to restart... */
+ __put_super(sb);
+ return 1;
+ }
+ /* can't be the last, since s_list is still in use */
+ sb->s_count--;
+ BUG_ON(sb->s_count == 0);
+ return 0;
+}
+
/**
* put_super - drop a temporary reference to superblock
* @s: superblock in question
@@ -229,7 +250,8 @@ void generic_shutdown_super(struct super_block *sb)
unlock_super(sb);
}
spin_lock(&sb_lock);
- list_del(&sb->s_list);
+ /* should be initialized for __put_super_and_need_restart() */
+ list_del_init(&sb->s_list);
list_del(&sb->s_instances);
spin_unlock(&sb_lock);
up_write(&sb->s_umount);
@@ -282,7 +304,7 @@ retry:
}
s->s_type = type;
strlcpy(s->s_id, type->name, sizeof(s->s_id));
- list_add(&s->s_list, super_blocks.prev);
+ list_add_tail(&s->s_list, &super_blocks);
list_add(&s->s_instances, &type->fs_supers);
spin_unlock(&sb_lock);
get_filesystem(type);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 76f96659507a..5bb9738362d5 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1149,6 +1149,7 @@ struct super_block *sget(struct file_system_type *type,
struct super_block *get_sb_pseudo(struct file_system_type *, char *,
struct super_operations *ops, unsigned long);
int __put_super(struct super_block *sb);
+int __put_super_and_need_restart(struct super_block *sb);
void unnamed_dev_init(void);
/* Alas, no aliases. Too much hassle with bringing module.h everywhere */