]> git.hungrycats.org Git - linux/commitdiff
[PATCH] Fix writeback_inodes-vs-umount race
authorAndrew Morton <akpm@osdl.org>
Sat, 15 May 2004 03:12:33 +0000 (20:12 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Sat, 15 May 2004 03:12:33 +0000 (20:12 -0700)
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.

fs/fs-writeback.c
fs/super.c
include/linux/fs.h

index ff91893977e425334cedeecaecb32184d38cd7a4..aa414468bda71648a312ea22023152de05fb0475 100644 (file)
@@ -397,12 +397,16 @@ writeback_inodes(struct writeback_control *wbc)
 
        spin_lock(&inode_lock);
        spin_lock(&sb_lock);
+restart:
        sb = sb_entry(super_blocks.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)) {
+                       sb->s_count++;
                        spin_unlock(&sb_lock);
                        sync_sb_inodes(sb, wbc);
                        spin_lock(&sb_lock);
+                       if (__put_super(sb))
+                               goto restart;
                }
                if (wbc->nr_to_write <= 0)
                        break;
index 12427a6e7f9a688f19ce0d1b81ea76e2306af15c..8d9bff178bcac6ea625e79b2aa7a95d78153bba0 100644 (file)
@@ -101,6 +101,21 @@ static inline void destroy_super(struct super_block *s)
 
 /* 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
  *     @s: superblock in question
@@ -108,14 +123,14 @@ static inline void destroy_super(struct super_block *s)
  *     Drops a temporary reference, frees superblock if there's no
  *     references left.
  */
-static inline void put_super(struct super_block *s)
+static void put_super(struct super_block *sb)
 {
        spin_lock(&sb_lock);
-       if (!--s->s_count)
-               destroy_super(s);
+       __put_super(sb);
        spin_unlock(&sb_lock);
 }
 
+
 /**
  *     deactivate_super        -       drop an active reference to superblock
  *     @s: superblock to deactivate
index 8971ae34dbdf499a0ca611f9eece3aaef3a63b78..b1734b045c6b415a5319971375beebc020f17196 100644 (file)
@@ -1114,6 +1114,7 @@ struct super_block *sget(struct file_system_type *type,
                        void *data);
 struct super_block *get_sb_pseudo(struct file_system_type *, char *,
                        struct super_operations *ops, unsigned long);
+int __put_super(struct super_block *sb);
 void unnamed_dev_init(void);
 
 /* Alas, no aliases. Too much hassle with bringing module.h everywhere */