Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 164843C4B93; Tue, 21 Jul 2026 21:52:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784670729; cv=none; b=Vvr8V6Or9BKJedrPfE6O5YsNxxiR9rZ89edsihOPWnVu3IuH88NPWDeSW2lRjUSX0WuqZEtR3ltsE5803XkmgNQgqExOuikoC7fCBirR3C4EEAe8zZqZK/EZoTspQva1tzaAZyxasT/tFpykARroO03tVN8nrTowd1zXjY/DPFk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784670729; c=relaxed/simple; bh=y9r4hPVlPXUgBV+moVo2njEsZ+UL3SVbpAIDtNoM9TM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dbThN8Mm+Dya1BVEKwhBl60URy3rlsFkA+wJh7ps39O7fUkjALgkw723W2hCzaGSKxZ46nNpf9P2cD7gjnh2eePtBAChUhalnvBd6eP1P+h70yHuopwJ8Jzt0YamWdHSiOqaoQ+/VQNq7H2ovQrJSDEO6QHLn44z1dIn3PE9p0U= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=WGQXP6tL; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="WGQXP6tL" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7BF111F000E9; Tue, 21 Jul 2026 21:52:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1784670728; bh=RfWTfHM2zWeMFfOt1QShGDTqdWFYAcb6rbS1WUXtoRA=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=WGQXP6tLvNYCy8Lx/SBfFwcj7MFKFDfR5gDFpSQ2mgVb+PdjtNbI9McXjFs39/SV0 nbQZ8bXwaROM0ryMDIeOrv3ue17oMDRkrX7jSHkvQLZ2LUBTwGnrs3aBHe5OskYAXq vG/HQnvlKooA/mfmflEfD+WbyJQeZD57Z/SIz6NA= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Boris Burkov , Qu Wenruo , David Sterba , Sasha Levin Subject: [PATCH 6.1 1042/1067] btrfs: fix false IO failure after falling back to buffered write Date: Tue, 21 Jul 2026 17:27:23 +0200 Message-ID: <20260721152447.899783904@linuxfoundation.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260721152424.521567757@linuxfoundation.org> References: <20260721152424.521567757@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.1-stable review patch. If anyone has any objections, please let me know. ------------------ From: Qu Wenruo [ Upstream commit 66ff4d366e7eb4d31813d2acabf3af512ce03aa5 ] [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 f85781fb505e ("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 d61bec08b904 ("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 Signed-off-by: Qu Wenruo Signed-off-by: David Sterba Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/inode.c | 33 +++++++++++++++++++++++++-------- fs/btrfs/ordered-data.c | 12 ++++++++++++ fs/btrfs/ordered-data.h | 2 ++ 3 files changed, 39 insertions(+), 8 deletions(-) --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -8031,12 +8031,33 @@ static int btrfs_dio_iomap_end(struct in if (submitted < length) { pos += submitted; length -= submitted; - if (write) + if (write) { + struct btrfs_ordered_extent *ordered; + + /* + * We have a short write, and the ordered extent is + * not split at submission time on this tree, so it + * may cover both the submitted range and the range + * that was never submitted. + * + * Truncate the ordered extent to the part that was + * submitted properly, so the never submitted range + * is reclaimed without inserting a file extent item + * for it and without marking it as an IO error. + */ + ordered = btrfs_lookup_ordered_extent(BTRFS_I(inode), + pos); + if (ordered) { + btrfs_mark_ordered_extent_truncated(ordered, + pos - ordered->file_offset); + btrfs_put_ordered_extent(ordered); + } btrfs_mark_ordered_io_finished(BTRFS_I(inode), NULL, - pos, length, false); - else + pos, length, true); + } else { unlock_extent(&BTRFS_I(inode)->io_tree, pos, pos + length - 1, NULL); + } ret = -ENOTBLK; } @@ -8560,11 +8581,7 @@ static void btrfs_invalidate_folio(struc EXTENT_LOCKED | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, &cached_state); - spin_lock_irq(&inode->ordered_tree.lock); - set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); - ordered->truncated_len = min(ordered->truncated_len, - cur - ordered->file_offset); - spin_unlock_irq(&inode->ordered_tree.lock); + btrfs_mark_ordered_extent_truncated(ordered, cur - ordered->file_offset); /* * If the ordered extent has finished, we're safe to delete all --- a/fs/btrfs/ordered-data.c +++ b/fs/btrfs/ordered-data.c @@ -279,6 +279,18 @@ void btrfs_add_ordered_sum(struct btrfs_ spin_unlock_irq(&tree->lock); } +void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, + u64 truncate_len) +{ + struct btrfs_inode *inode = BTRFS_I(ordered->inode); + + ASSERT(truncate_len <= ordered->num_bytes); + spin_lock_irq(&inode->ordered_tree.lock); + set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); + ordered->truncated_len = min(ordered->truncated_len, truncate_len); + spin_unlock_irq(&inode->ordered_tree.lock); +} + static void finish_ordered_fn(struct btrfs_work *work) { struct btrfs_ordered_extent *ordered_extent; --- a/fs/btrfs/ordered-data.h +++ b/fs/btrfs/ordered-data.h @@ -209,6 +209,8 @@ void btrfs_lock_and_flush_ordered_range( bool btrfs_try_lock_ordered_range(struct btrfs_inode *inode, u64 start, u64 end); int btrfs_split_ordered_extent(struct btrfs_ordered_extent *ordered, u64 pre, u64 post); +void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, + u64 truncate_len); int __init ordered_data_init(void); void __cold ordered_data_exit(void);