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>
This commit is contained in:
parent
646caa9c8e
commit
4743f83990
|
@ -4327,20 +4327,6 @@ static int ext4_commit_super(struct super_block *sb, int sync)
|
||||||
|
|
||||||
if (!sbh || block_device_ejected(sb))
|
if (!sbh || block_device_ejected(sb))
|
||||||
return error;
|
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
|
* If the file system is mounted read-only, don't update the
|
||||||
* superblock write time. This avoids updating the superblock
|
* 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));
|
&EXT4_SB(sb)->s_freeinodes_counter));
|
||||||
BUFFER_TRACE(sbh, "marking dirty");
|
BUFFER_TRACE(sbh, "marking dirty");
|
||||||
ext4_superblock_csum_set(sb);
|
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);
|
mark_buffer_dirty(sbh);
|
||||||
|
unlock_buffer(sbh);
|
||||||
if (sync) {
|
if (sync) {
|
||||||
error = __sync_dirty_buffer(sbh,
|
error = __sync_dirty_buffer(sbh,
|
||||||
test_opt(sb, BARRIER) ? WRITE_FUA : WRITE_SYNC);
|
test_opt(sb, BARRIER) ? WRITE_FUA : WRITE_SYNC);
|
||||||
|
|
Loading…
Reference in New Issue