From 74e8091130dced6399454510a35cb0e52c40623b Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Fri, 21 Jul 2023 16:35:21 +1000 Subject: [PATCH] abd_bio_map_off: avoid splitting scatter pages across bios This is the same change as the previous commit, but for scatter abds. Its less clear if this change is needed. Since scatter abds are only ever added a page at time, both sides of the split should always be added in consecutive segments. Intuitively though, it may be possible for a partially-filled bio to be used, or a bio with an odd number of iovecs, and that then could lead to a misaligned bio. While I've not been able to reproduce this at all, it seems to make sense to protect against it. Signed-off-by: Rob Norris (cherry picked from commit cbdf21fd1a32a5e696a22cad497d9211221fa309) --- module/os/linux/zfs/abd_os.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/module/os/linux/zfs/abd_os.c b/module/os/linux/zfs/abd_os.c index 5b39394de7..2a31111bb3 100644 --- a/module/os/linux/zfs/abd_os.c +++ b/module/os/linux/zfs/abd_os.c @@ -1143,7 +1143,9 @@ abd_bio_map_off(struct bio *bio, abd_t *abd, abd_iter_init(&aiter, abd); abd_iter_advance(&aiter, off); - for (int i = 0; i < bio->bi_max_vecs; i++) { + boolean_t is_split = B_FALSE; + unsigned int split_rem = 0; + for (int i = bio->bi_vcnt; i < bio->bi_max_vecs; i++) { struct page *pg; size_t len, sgoff, pgoff; struct scatterlist *sg; @@ -1154,7 +1156,36 @@ abd_bio_map_off(struct bio *bio, abd_t *abd, sg = aiter.iter_sg; sgoff = aiter.iter_offset; pgoff = sgoff & (PAGESIZE - 1); - len = MIN(io_size, PAGESIZE - pgoff); + + if (is_split && + (io_size <= split_rem || ((i + 1) == bio->bi_max_vecs))) { + /* + * Last segment and we're split, so we have to add just + * enough to restore alignment. + */ + len = split_rem; + is_split = B_FALSE; + } + else if (pgoff != 0) { + /* + * This is a split page, so we need to ensure that we + * have room for the tail within this bio. + */ + if ((i + 1) == bio->bi_max_vecs) + break; + + /* Take up to the end of the page */ + len = PAGE_SIZE - pgoff; + + is_split = B_TRUE; + split_rem = pgoff; + } + else + len = PAGE_SIZE; + + if (len > io_size) + len = io_size; + ASSERT(len > 0); pg = nth_page(sg_page(sg), sgoff >> PAGE_SHIFT);