Skip to content

Commit 66ff4d3

Browse files
adam900710morbidrsa
authored andcommitted
btrfs: fix false IO failure after falling back to buffered write
[BUG] The test case generic/362 will fail with "nodatasum" mount option (*): MOUNT_OPTIONS -- -o nodatasum /dev/mapper/test-scratch1 /mnt/scratch generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930 +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:21:17.574771567 +0930 @@ -1,2 +1,3 @@ QA output created by 362 +First write failed: Input/output error Silence is golden ... *: If the test case has been executed before with default data checksum, the failure will not reproduce. Need the following fix to make it reliably reproducible: https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/ [CAUSE] Inside __iomap_dio_rw(), the -EFAULT/-ENOTBLK error is not directly returned. Thus we never got an error pointer from __iomap_dio_rw(). The call chain looks like this: btrfs_direct_write() |- btrfs_dio_write() |- __iomap_dio_rw() | |- iomap_iter() | | |- btrfs_dio_iomap_begin() | | Now an ordered extent is allocated for the 4K write. | | | |- iomi.status = iomap_dio_iter() | | Where iomap_dio_iter() returned -EFAULT. | | | |- ret = iomap_iter() | | |- btrfs_dio_iomap_end() | | | |- btrfs_finish_ordered_extent(uptodate = false) | | | | |- can_finish_ordered_extent() | | | | |- btrfs_mark_ordered_extent_error() | | | | |- mapping_set_error() | | | | Now the address space is marked error. | | | | return -ENOTBLK | | |- return -ENOTBLK | |- if (ret == -ENOTBLK) { ret = 0; } | Now the return value is reset to 0. | Thus no error pointer will be returned. | |- ret = iomap_dio_complete() | Since no byte is submitted, @ret is 0. | |- Fallback to buffered IO | And the buffered write finished without error | |- filemap_fdatawait_range() |- filemap_check_errors() The previous error is recorded, thus an error is returned However the buffered write is properly submitted and finished, the error is from the btrfs_finish_ordered_extent() call with @uptodate = false. [FIX] When a short dio write happened, any range that is submitted will have btrfs_extract_ordered_extent() to be called, thus the submitted range will always have an OE just covering the submitted range. The remaining OE range is never submitted, thus they should be treated as truncated, not an error. So that we can properly reclaim and not insert an unnecessary file extent item, without marking the mapping as error. Extract a helper, btrfs_mark_ordered_extent_truncated(), and utilize that helper to mark the direct IO ordered extent as truncated, so it won't cause failure for the later buffered fallback. [REASON FOR NO FIXES TAG] The bug itself is pretty old, at commit f85781f ("btrfs: switch to iomap for direct IO") we're already passing @uptodate=false finishing the OE. But at that time OE with IOERR won't call mapping_set_error(), so it's not exposed. Later commit d61bec0 ("btrfs: mark ordered extent and inode with error if we fail to finish") finally exposed the bug, but that commit is doing a correct job, not the root cause. Anyway the bug is very old, dating back to 5.1x days, thus only CC to stable. CC: stable@vger.kernel.org # 5.15+ Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
1 parent c4e7778 commit 66ff4d3

4 files changed

Lines changed: 29 additions & 8 deletions

File tree

fs/btrfs/direct-io.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,12 +624,23 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length,
624624
if (submitted < length) {
625625
pos += submitted;
626626
length -= submitted;
627-
if (write)
627+
if (write) {
628+
/*
629+
* We have a short write, if there is any range
630+
* that is submitted properly, that part will have
631+
* its own OE split from the original one.
632+
*
633+
* So for the OE at dio_data->ordered, it's the part
634+
* that is not submitted, and should be marked
635+
* as fully truncated.
636+
*/
637+
btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0);
628638
btrfs_finish_ordered_extent(dio_data->ordered,
629-
pos, length, false);
630-
else
639+
pos, length, true);
640+
} else {
631641
btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos,
632642
pos + length - 1, NULL);
643+
}
633644
ret = -ENOTBLK;
634645
}
635646
if (write) {

fs/btrfs/inode.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7590,11 +7590,7 @@ static void btrfs_invalidate_folio(struct folio *folio, size_t offset,
75907590
EXTENT_LOCKED | EXTENT_DO_ACCOUNTING |
75917591
EXTENT_DEFRAG, &cached_state);
75927592

7593-
spin_lock(&inode->ordered_tree_lock);
7594-
set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags);
7595-
ordered->truncated_len = min(ordered->truncated_len,
7596-
cur - ordered->file_offset);
7597-
spin_unlock(&inode->ordered_tree_lock);
7593+
btrfs_mark_ordered_extent_truncated(ordered, cur - ordered->file_offset);
75987594

75997595
/*
76007596
* If the ordered extent has finished, we're safe to delete all

fs/btrfs/ordered-data.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,18 @@ void btrfs_mark_ordered_extent_error(struct btrfs_ordered_extent *ordered)
358358
mapping_set_error(ordered->inode->vfs_inode.i_mapping, -EIO);
359359
}
360360

361+
void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered,
362+
u64 truncate_len)
363+
{
364+
struct btrfs_inode *inode = ordered->inode;
365+
366+
ASSERT(truncate_len <= ordered->num_bytes);
367+
spin_lock(&inode->ordered_tree_lock);
368+
set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags);
369+
ordered->truncated_len = min(ordered->truncated_len, truncate_len);
370+
spin_unlock(&inode->ordered_tree_lock);
371+
}
372+
361373
static void finish_ordered_fn(struct btrfs_work *work)
362374
{
363375
struct btrfs_ordered_extent *ordered_extent;

fs/btrfs/ordered-data.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ bool btrfs_try_lock_ordered_range(struct btrfs_inode *inode, u64 start, u64 end,
226226
struct btrfs_ordered_extent *btrfs_split_ordered_extent(
227227
struct btrfs_ordered_extent *ordered, u64 len);
228228
void btrfs_mark_ordered_extent_error(struct btrfs_ordered_extent *ordered);
229+
void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered,
230+
u64 truncate_len);
229231
int __init ordered_data_init(void);
230232
void __cold ordered_data_exit(void);
231233

0 commit comments

Comments
 (0)