| From: "Pranay Kr. Srivastava" <pranjas@gmail.com> |
| Date: Mon, 4 Jul 2016 10:24:52 -0400 |
| Subject: ext4: Fix WARN_ON_ONCE in ext4_commit_super() |
| |
| commit 4743f83990614af6adb09ea7aa3c37b78c4031ab upstream. |
| |
| 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> |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| fs/ext4/super.c | 30 ++++++++++++++++-------------- |
| 1 file changed, 16 insertions(+), 14 deletions(-) |
| |
| --- a/fs/ext4/super.c |
| +++ b/fs/ext4/super.c |
| @@ -4653,20 +4653,6 @@ static int ext4_commit_super(struct supe |
| |
| 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 |
| @@ -4695,7 +4681,23 @@ static int ext4_commit_super(struct supe |
| &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); |
| if (error) |