ext4: Fix WARN_ON_ONCE in ext4_commit_super()
authorPranay Kr. Srivastava <pranjas@gmail.com>
Mon, 4 Jul 2016 14:24:52 +0000 (10:24 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Mon, 4 Jul 2016 14:24:52 +0000 (10:24 -0400)
If there are racing calls to ext4_commit_super() it's possible for
another writeback of the superblock to result in the buffer being
marked with an error after we check if the buffer is marked as having
a write error and the buffer up-to-date flag is set again.  If that
happens mark_buffer_dirty() can end up throwing a WARN_ON_ONCE.

Fix this by moving this check to write before we call
write_buffer_dirty(), and keeping the buffer locked during this whole
sequence.

Signed-off-by: Pranay Kr. Srivastava <pranjas@gmail.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/super.c

index 6e2f9d6..5664ee6 100644 (file)
@@ -4327,20 +4327,6 @@ static int ext4_commit_super(struct super_block *sb, int sync)
 
        if (!sbh || block_device_ejected(sb))
                return error;
-       if (buffer_write_io_error(sbh)) {
-               /*
-                * Oh, dear.  A previous attempt to write the
-                * superblock failed.  This could happen because the
-                * USB device was yanked out.  Or it could happen to
-                * be a transient write error and maybe the block will
-                * be remapped.  Nothing we can do but to retry the
-                * write and hope for the best.
-                */
-               ext4_msg(sb, KERN_ERR, "previous I/O error to "
-                      "superblock detected");
-               clear_buffer_write_io_error(sbh);
-               set_buffer_uptodate(sbh);
-       }
        /*
         * If the file system is mounted read-only, don't update the
         * superblock write time.  This avoids updating the superblock
@@ -4371,7 +4357,23 @@ static int ext4_commit_super(struct super_block *sb, int sync)
                                &EXT4_SB(sb)->s_freeinodes_counter));
        BUFFER_TRACE(sbh, "marking dirty");
        ext4_superblock_csum_set(sb);
+       lock_buffer(sbh);
+       if (buffer_write_io_error(sbh)) {
+               /*
+                * Oh, dear.  A previous attempt to write the
+                * superblock failed.  This could happen because the
+                * USB device was yanked out.  Or it could happen to
+                * be a transient write error and maybe the block will
+                * be remapped.  Nothing we can do but to retry the
+                * write and hope for the best.
+                */
+               ext4_msg(sb, KERN_ERR, "previous I/O error to "
+                      "superblock detected");
+               clear_buffer_write_io_error(sbh);
+               set_buffer_uptodate(sbh);
+       }
        mark_buffer_dirty(sbh);
+       unlock_buffer(sbh);
        if (sync) {
                error = __sync_dirty_buffer(sbh,
                        test_opt(sb, BARRIER) ? WRITE_FUA : WRITE_SYNC);