diff mbox series

[09/10] ext4: abort the filesystem while freeing the write error io buffer

Message ID 20200526071754.33819-10-yi.zhang@huawei.com
State New
Headers show
Series ext4: fix inconsistency since reading old metadata from disk | expand

Commit Message

zhangyi (F) May 26, 2020, 7:17 a.m. UTC
Now we can prevent reading old metadata buffer from the disk which has
been failed to write out through checking write io error when getting
the buffer. One more thing need to do is to prevent freeing the write
io error buffer. If the buffer was freed, we lose the latest data and
buffer stats, finally it will also lead to inconsistency.

So, this patch abort the journal in journal mode and invoke
ext4_error_err() in nojournal mode to prevent further inconsistency.

Signed-off-by: zhangyi (F) <yi.zhang@huawei.com>
---
 fs/ext4/super.c       | 32 +++++++++++++++++++++++++++++++-
 fs/jbd2/transaction.c | 13 +++++++++++++
 2 files changed, 44 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index d25a0fe44bec..1e15179aa1c4 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1349,6 +1349,36 @@  static int ext4_nfs_commit_metadata(struct inode *inode)
 	return ext4_write_inode(inode, &wbc);
 }
 
+static int bdev_try_to_free_buffer(struct super_block *sb, struct page *page)
+{
+	struct buffer_head *head, *bh;
+	bool has_write_io_error = false;
+
+	head = page_buffers(page);
+	bh = head;
+	do {
+		/*
+		 * If the buffer has been failed to write out, the metadata
+		 * in this buffer is uptodate but which on disk is old, may
+		 * lead to inconsistency while reading the old data
+		 * successfully.
+		 */
+		if (buffer_write_io_error(bh) && !buffer_uptodate(bh)) {
+			has_write_io_error = true;
+			break;
+		}
+	} while ((bh = bh->b_this_page) != head);
+
+	if (has_write_io_error)
+		ext4_error_err(sb, EIO, "Free metadata buffer (%llu) that has "
+			       "been failed to write out. There is a risk of "
+			       "filesystem inconsistency in case of reading "
+			       "metadata from this block subsequently.",
+			       (unsigned long long) bh->b_blocknr);
+
+	return try_to_free_buffers(page);
+}
+
 /*
  * Try to release metadata pages (indirect blocks, directories) which are
  * mapped via the block device.  Since these pages could have journal heads
@@ -1366,7 +1396,7 @@  static int bdev_try_to_free_page(struct super_block *sb, struct page *page,
 	if (journal)
 		return jbd2_journal_try_to_free_buffers(journal, page,
 						wait & ~__GFP_DIRECT_RECLAIM);
-	return try_to_free_buffers(page);
+	return bdev_try_to_free_buffer(sb, page);
 }
 
 #ifdef CONFIG_FS_ENCRYPTION
diff --git a/fs/jbd2/transaction.c b/fs/jbd2/transaction.c
index 3dccc23cf010..ac6a077afec3 100644
--- a/fs/jbd2/transaction.c
+++ b/fs/jbd2/transaction.c
@@ -2109,6 +2109,7 @@  int jbd2_journal_try_to_free_buffers(journal_t *journal,
 {
 	struct buffer_head *head;
 	struct buffer_head *bh;
+	bool has_write_io_error = false;
 	int ret = 0;
 
 	J_ASSERT(PageLocked(page));
@@ -2133,11 +2134,23 @@  int jbd2_journal_try_to_free_buffers(journal_t *journal,
 		jbd2_journal_put_journal_head(jh);
 		if (buffer_jbd(bh))
 			goto busy;
+
+		/*
+		 * If the buffer has been failed to write out, the metadata
+		 * in this buffer is uptodate but which on disk is old,
+		 * abort journal to prevent subsequent inconsistency while
+		 * reading the old data successfully.
+		 */
+		if (buffer_write_io_error(bh) && !buffer_uptodate(bh))
+			has_write_io_error = true;
 	} while ((bh = bh->b_this_page) != head);
 
 	ret = try_to_free_buffers(page);
 
 busy:
+	if (has_write_io_error)
+		jbd2_journal_abort(journal, -EIO);
+
 	return ret;
 }