diff options
author | Pranay Kr. Srivastava <pranjas@gmail.com> | 2016-07-04 10:24:52 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2016-07-04 10:24:52 -0400 |
commit | 4743f83990614af6adb09ea7aa3c37b78c4031ab (patch) | |
tree | 86784086a26c2d2181270426145540310de8acd0 | |
parent | 646caa9c8e196880b41cd3e3d33a2ebc752bdb85 (diff) |
ext4: Fix WARN_ON_ONCE in ext4_commit_super()
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>
-rw-r--r-- | fs/ext4/super.c | 30 |
1 files changed, 16 insertions, 14 deletions
diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 6e2f9d628c48..5664ee66b301 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c | |||
@@ -4327,20 +4327,6 @@ static int ext4_commit_super(struct super_block *sb, int sync) | |||
4327 | 4327 | ||
4328 | if (!sbh || block_device_ejected(sb)) | 4328 | if (!sbh || block_device_ejected(sb)) |
4329 | return error; | 4329 | return error; |
4330 | if (buffer_write_io_error(sbh)) { | ||
4331 | /* | ||
4332 | * Oh, dear. A previous attempt to write the | ||
4333 | * superblock failed. This could happen because the | ||
4334 | * USB device was yanked out. Or it could happen to | ||
4335 | * be a transient write error and maybe the block will | ||
4336 | * be remapped. Nothing we can do but to retry the | ||
4337 | * write and hope for the best. | ||
4338 | */ | ||
4339 | ext4_msg(sb, KERN_ERR, "previous I/O error to " | ||
4340 | "superblock detected"); | ||
4341 | clear_buffer_write_io_error(sbh); | ||
4342 | set_buffer_uptodate(sbh); | ||
4343 | } | ||
4344 | /* | 4330 | /* |
4345 | * If the file system is mounted read-only, don't update the | 4331 | * If the file system is mounted read-only, don't update the |
4346 | * superblock write time. This avoids updating the superblock | 4332 | * superblock write time. This avoids updating the superblock |
@@ -4371,7 +4357,23 @@ static int ext4_commit_super(struct super_block *sb, int sync) | |||
4371 | &EXT4_SB(sb)->s_freeinodes_counter)); | 4357 | &EXT4_SB(sb)->s_freeinodes_counter)); |
4372 | BUFFER_TRACE(sbh, "marking dirty"); | 4358 | BUFFER_TRACE(sbh, "marking dirty"); |
4373 | ext4_superblock_csum_set(sb); | 4359 | ext4_superblock_csum_set(sb); |
4360 | lock_buffer(sbh); | ||
4361 | if (buffer_write_io_error(sbh)) { | ||
4362 | /* | ||
4363 | * Oh, dear. A previous attempt to write the | ||
4364 | * superblock failed. This could happen because the | ||
4365 | * USB device was yanked out. Or it could happen to | ||
4366 | * be a transient write error and maybe the block will | ||
4367 | * be remapped. Nothing we can do but to retry the | ||
4368 | * write and hope for the best. | ||
4369 | */ | ||
4370 | ext4_msg(sb, KERN_ERR, "previous I/O error to " | ||
4371 | "superblock detected"); | ||
4372 | clear_buffer_write_io_error(sbh); | ||
4373 | set_buffer_uptodate(sbh); | ||
4374 | } | ||
4374 | mark_buffer_dirty(sbh); | 4375 | mark_buffer_dirty(sbh); |
4376 | unlock_buffer(sbh); | ||
4375 | if (sync) { | 4377 | if (sync) { |
4376 | error = __sync_dirty_buffer(sbh, | 4378 | error = __sync_dirty_buffer(sbh, |
4377 | test_opt(sb, BARRIER) ? WRITE_FUA : WRITE_SYNC); | 4379 | test_opt(sb, BARRIER) ? WRITE_FUA : WRITE_SYNC); |