Skip to content

Commit cb3de5f

Browse files
Shida Zhangtytso
authored andcommitted
ext4: fix a potential assertion failure due to improperly dirtied buffer
On an old kernel version(4.19, ext3, data=journal, pagesize=64k), an assertion failure will occasionally be triggered by the line below: ----------- jbd2_journal_commit_transaction { ... J_ASSERT_BH(bh, !buffer_dirty(bh)); /* * The buffer on BJ_Forget list and not jbddirty means ... } ----------- The same condition may also be applied to the lattest kernel version. When blocksize < pagesize and we truncate a file, there can be buffers in the mapping tail page beyond i_size. These buffers will be filed to transaction's BJ_Forget list by ext4_journalled_invalidatepage() during truncation. When the transaction doing truncate starts committing, we can grow the file again. This calls __block_write_begin() which allocates new blocks under these buffers in the tail page we go through the branch: if (buffer_new(bh)) { clean_bdev_bh_alias(bh); if (folio_test_uptodate(folio)) { clear_buffer_new(bh); set_buffer_uptodate(bh); mark_buffer_dirty(bh); continue; } ... } Hence buffers on BJ_Forget list of the committing transaction get marked dirty and this triggers the jbd2 assertion. Teach ext4_block_write_begin() to properly handle files with data journalling by avoiding dirtying them directly. Instead of folio_zero_new_buffers() we use ext4_journalled_zero_new_buffers() which takes care of handling journalling. We also don't need to mark new uptodate buffers as dirty in ext4_block_write_begin(). That will be either done either by block_commit_write() in case of success or by folio_zero_new_buffers() in case of failure. Reported-by: Baolin Liu <[email protected]> Suggested-by: Jan Kara <[email protected]> Signed-off-by: Shida Zhang <[email protected]> Reviewed-by: Jan Kara <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Theodore Ts'o <[email protected]>
1 parent 6b730a4 commit cb3de5f

File tree

3 files changed

+40
-12
lines changed

3 files changed

+40
-12
lines changed

fs/ext4/ext4.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3853,7 +3853,8 @@ static inline int ext4_buffer_uptodate(struct buffer_head *bh)
38533853
return buffer_uptodate(bh);
38543854
}
38553855

3856-
extern int ext4_block_write_begin(struct folio *folio, loff_t pos, unsigned len,
3856+
extern int ext4_block_write_begin(handle_t *handle, struct folio *folio,
3857+
loff_t pos, unsigned len,
38573858
get_block_t *get_block);
38583859
#endif /* __KERNEL__ */
38593860

fs/ext4/inline.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,10 +601,11 @@ static int ext4_convert_inline_data_to_extent(struct address_space *mapping,
601601
goto out;
602602

603603
if (ext4_should_dioread_nolock(inode)) {
604-
ret = ext4_block_write_begin(folio, from, to,
604+
ret = ext4_block_write_begin(handle, folio, from, to,
605605
ext4_get_block_unwritten);
606606
} else
607-
ret = ext4_block_write_begin(folio, from, to, ext4_get_block);
607+
ret = ext4_block_write_begin(handle, folio, from, to,
608+
ext4_get_block);
608609

609610
if (!ret && ext4_should_journal_data(inode)) {
610611
ret = ext4_walk_page_buffers(handle, inode,
@@ -856,7 +857,7 @@ static int ext4_da_convert_inline_data_to_extent(struct address_space *mapping,
856857
goto out;
857858
}
858859

859-
ret = ext4_block_write_begin(folio, 0, inline_size,
860+
ret = ext4_block_write_begin(NULL, folio, 0, inline_size,
860861
ext4_da_get_block_prep);
861862
if (ret) {
862863
up_read(&EXT4_I(inode)->xattr_sem);

fs/ext4/inode.c

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949

5050
#include <trace/events/ext4.h>
5151

52+
static void ext4_journalled_zero_new_buffers(handle_t *handle,
53+
struct inode *inode,
54+
struct folio *folio,
55+
unsigned from, unsigned to);
56+
5257
static __u32 ext4_inode_csum(struct inode *inode, struct ext4_inode *raw,
5358
struct ext4_inode_info *ei)
5459
{
@@ -1023,7 +1028,8 @@ int do_journal_get_write_access(handle_t *handle, struct inode *inode,
10231028
return ret;
10241029
}
10251030

1026-
int ext4_block_write_begin(struct folio *folio, loff_t pos, unsigned len,
1031+
int ext4_block_write_begin(handle_t *handle, struct folio *folio,
1032+
loff_t pos, unsigned len,
10271033
get_block_t *get_block)
10281034
{
10291035
unsigned from = pos & (PAGE_SIZE - 1);
@@ -1037,6 +1043,7 @@ int ext4_block_write_begin(struct folio *folio, loff_t pos, unsigned len,
10371043
struct buffer_head *bh, *head, *wait[2];
10381044
int nr_wait = 0;
10391045
int i;
1046+
bool should_journal_data = ext4_should_journal_data(inode);
10401047

10411048
BUG_ON(!folio_test_locked(folio));
10421049
BUG_ON(from > PAGE_SIZE);
@@ -1066,10 +1073,22 @@ int ext4_block_write_begin(struct folio *folio, loff_t pos, unsigned len,
10661073
if (err)
10671074
break;
10681075
if (buffer_new(bh)) {
1076+
/*
1077+
* We may be zeroing partial buffers or all new
1078+
* buffers in case of failure. Prepare JBD2 for
1079+
* that.
1080+
*/
1081+
if (should_journal_data)
1082+
do_journal_get_write_access(handle,
1083+
inode, bh);
10691084
if (folio_test_uptodate(folio)) {
1070-
clear_buffer_new(bh);
1085+
/*
1086+
* Unlike __block_write_begin() we leave
1087+
* dirtying of new uptodate buffers to
1088+
* ->write_end() time or
1089+
* folio_zero_new_buffers().
1090+
*/
10711091
set_buffer_uptodate(bh);
1072-
mark_buffer_dirty(bh);
10731092
continue;
10741093
}
10751094
if (block_end > to || block_start < from)
@@ -1099,7 +1118,11 @@ int ext4_block_write_begin(struct folio *folio, loff_t pos, unsigned len,
10991118
err = -EIO;
11001119
}
11011120
if (unlikely(err)) {
1102-
folio_zero_new_buffers(folio, from, to);
1121+
if (should_journal_data)
1122+
ext4_journalled_zero_new_buffers(handle, inode, folio,
1123+
from, to);
1124+
else
1125+
folio_zero_new_buffers(folio, from, to);
11031126
} else if (fscrypt_inode_uses_fs_layer_crypto(inode)) {
11041127
for (i = 0; i < nr_wait; i++) {
11051128
int err2;
@@ -1197,10 +1220,11 @@ static int ext4_write_begin(struct file *file, struct address_space *mapping,
11971220
folio_wait_stable(folio);
11981221

11991222
if (ext4_should_dioread_nolock(inode))
1200-
ret = ext4_block_write_begin(folio, pos, len,
1223+
ret = ext4_block_write_begin(handle, folio, pos, len,
12011224
ext4_get_block_unwritten);
12021225
else
1203-
ret = ext4_block_write_begin(folio, pos, len, ext4_get_block);
1226+
ret = ext4_block_write_begin(handle, folio, pos, len,
1227+
ext4_get_block);
12041228
if (!ret && ext4_should_journal_data(inode)) {
12051229
ret = ext4_walk_page_buffers(handle, inode,
12061230
folio_buffers(folio), from, to,
@@ -2926,7 +2950,8 @@ static int ext4_da_write_begin(struct file *file, struct address_space *mapping,
29262950
if (IS_ERR(folio))
29272951
return PTR_ERR(folio);
29282952

2929-
ret = ext4_block_write_begin(folio, pos, len, ext4_da_get_block_prep);
2953+
ret = ext4_block_write_begin(NULL, folio, pos, len,
2954+
ext4_da_get_block_prep);
29302955
if (ret < 0) {
29312956
folio_unlock(folio);
29322957
folio_put(folio);
@@ -6183,7 +6208,8 @@ vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf)
61836208
if (folio_pos(folio) + len > size)
61846209
len = size - folio_pos(folio);
61856210

6186-
err = __block_write_begin(&folio->page, 0, len, ext4_get_block);
6211+
err = ext4_block_write_begin(handle, folio, 0, len,
6212+
ext4_get_block);
61876213
if (!err) {
61886214
ret = VM_FAULT_SIGBUS;
61896215
if (ext4_journal_folio_buffers(handle, folio, len))

0 commit comments

Comments
 (0)