]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
block: revert the iov_iter after a short copy in bio_iov_iter_bounce_write()
authorQu Wenruo <wqu@suse.com>
Tue, 16 Jun 2026 08:12:35 +0000 (17:42 +0930)
committerJens Axboe <axboe@kernel.dk>
Tue, 16 Jun 2026 20:48:35 +0000 (14:48 -0600)
For the incoming IOMAP_DIO_BOUNCE flag usage inside btrfs, it's pretty
easy to hit short copy inside bio_iov_iter_bounce_write().

This is because btrfs has disabled page fault to avoid certain deadlock
during direct writes, and instead btrfs manually fault in the pages then
retry.

And inside bio_iov_iter_bounce_write(), if we hit a short write, we
didn't revert the iov_iter, which can cause problems like unexpected
garbage for the next retry.

Revert the iov_iter after a short copy.

One thing to note is that, the folio is allocated then immediately
queued into the bio, so the proper revert size should be
(bi_size - this_len + copied).

Fixes: 8dd5e7c75d7b ("block: add helpers to bounce buffer an iov_iter into bios")
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/c400989f227343b134110773d5acaaacf7024574.1781597506.git.wqu@suse.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
block/bio.c

index 811a967962025ee32491ecc09303219ed9713073..96f40d39b62badf747400868f2f2bf375e548fad 100644 (file)
@@ -1321,6 +1321,7 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
 
        do {
                size_t this_len = min(total_len, SZ_1M);
+               size_t copied;
                struct folio *folio;
 
                if (this_len > minsize * 2)
@@ -1334,12 +1335,22 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
                        break;
                bio_add_folio_nofail(bio, folio, this_len, 0);
 
-               if (copy_from_iter(folio_address(folio), this_len, iter) !=
-                               this_len) {
+               copied = copy_from_iter(folio_address(folio), this_len, iter);
+               if (copied < this_len) {
+                       /*
+                        * Need to revert the iov iter for all bytes we have
+                        * copied.
+                        *
+                        * However the bio size differs from the real copied
+                        * bytes as @this_len is queued but only advanced
+                        * less than that.
+                        * Need to compensate that for the revert.
+                        */
+                       iov_iter_revert(iter, bio->bi_iter.bi_size - this_len +
+                                       copied);
                        bio_free_folios(bio);
                        return -EFAULT;
                }
-
                total_len -= this_len;
        } while (total_len && bio->bi_vcnt < bio->bi_max_vecs);