gfs2: Another gfs2_find_jhead fix
authorAndreas Gruenbacher <agruenba@redhat.com>
Sun, 8 Dec 2019 13:12:49 +0000 (13:12 +0000)
committerAndreas Gruenbacher <agruenba@redhat.com>
Tue, 7 Jan 2020 11:30:36 +0000 (12:30 +0100)
On filesystems with a block size smaller than the page size,
gfs2_find_jhead can split a page across two bios (for example, when
blocks are not allocated consecutively).  When that happens, the first
bio that completes will unlock the page in its bi_end_io handler even
though the page hasn't been read completely yet.  Fix that by using a
chained bio for the rest of the page.

While at it, clean up the sector calculation logic in
gfs2_log_alloc_bio.  In gfs2_find_jhead, simplify the disk block and
offset calculation logic and fix a variable name.

Fixes: f4686c26ecc3 ("gfs2: read journal in large chunks")
Cc: stable@vger.kernel.org # v5.2+
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
fs/gfs2/lops.c

index 55fed7daf2b1c19d044edfe6ff1342739374d316..aa0359b7e2aca0266ccb0391c237e561b0a5b5d2 100644 (file)
@@ -259,7 +259,7 @@ static struct bio *gfs2_log_alloc_bio(struct gfs2_sbd *sdp, u64 blkno,
        struct super_block *sb = sdp->sd_vfs;
        struct bio *bio = bio_alloc(GFP_NOIO, BIO_MAX_PAGES);
 
-       bio->bi_iter.bi_sector = blkno * (sb->s_blocksize >> 9);
+       bio->bi_iter.bi_sector = blkno << (sb->s_blocksize_bits - 9);
        bio_set_dev(bio, sb->s_bdev);
        bio->bi_end_io = end_io;
        bio->bi_private = sdp;
@@ -472,6 +472,20 @@ static void gfs2_jhead_process_page(struct gfs2_jdesc *jd, unsigned long index,
        put_page(page); /* Once more for find_or_create_page */
 }
 
+static struct bio *gfs2_chain_bio(struct bio *prev, unsigned int nr_iovecs)
+{
+       struct bio *new;
+
+       new = bio_alloc(GFP_NOIO, nr_iovecs);
+       bio_copy_dev(new, prev);
+       new->bi_iter.bi_sector = bio_end_sector(prev);
+       new->bi_opf = prev->bi_opf;
+       new->bi_write_hint = prev->bi_write_hint;
+       bio_chain(new, prev);
+       submit_bio(prev);
+       return new;
+}
+
 /**
  * gfs2_find_jhead - find the head of a log
  * @jd: The journal descriptor
@@ -488,15 +502,15 @@ int gfs2_find_jhead(struct gfs2_jdesc *jd, struct gfs2_log_header_host *head,
        struct gfs2_sbd *sdp = GFS2_SB(jd->jd_inode);
        struct address_space *mapping = jd->jd_inode->i_mapping;
        unsigned int block = 0, blocks_submitted = 0, blocks_read = 0;
-       unsigned int bsize = sdp->sd_sb.sb_bsize;
+       unsigned int bsize = sdp->sd_sb.sb_bsize, off;
        unsigned int bsize_shift = sdp->sd_sb.sb_bsize_shift;
        unsigned int shift = PAGE_SHIFT - bsize_shift;
-       unsigned int readhead_blocks = BIO_MAX_PAGES << shift;
+       unsigned int readahead_blocks = BIO_MAX_PAGES << shift;
        struct gfs2_journal_extent *je;
        int sz, ret = 0;
        struct bio *bio = NULL;
        struct page *page = NULL;
-       bool done = false;
+       bool bio_chained = false, done = false;
        errseq_t since;
 
        memset(head, 0, sizeof(*head));
@@ -505,9 +519,9 @@ int gfs2_find_jhead(struct gfs2_jdesc *jd, struct gfs2_log_header_host *head,
 
        since = filemap_sample_wb_err(mapping);
        list_for_each_entry(je, &jd->extent_list, list) {
-               for (; block < je->lblock + je->blocks; block++) {
-                       u64 dblock;
+               u64 dblock = je->dblock;
 
+               for (; block < je->lblock + je->blocks; block++, dblock++) {
                        if (!page) {
                                page = find_or_create_page(mapping,
                                                block >> shift, GFP_NOFS);
@@ -516,35 +530,41 @@ int gfs2_find_jhead(struct gfs2_jdesc *jd, struct gfs2_log_header_host *head,
                                        done = true;
                                        goto out;
                                }
+                               off = 0;
                        }
 
-                       if (bio) {
-                               unsigned int off;
-
-                               off = (block << bsize_shift) & ~PAGE_MASK;
+                       if (!bio || (bio_chained && !off)) {
+                               /* start new bio */
+                       } else {
                                sz = bio_add_page(bio, page, bsize, off);
-                               if (sz == bsize) { /* block added */
-                                       if (off + bsize == PAGE_SIZE) {
-                                               page = NULL;
-                                               goto page_added;
-                                       }
-                                       continue;
+                               if (sz == bsize)
+                                       goto block_added;
+                               if (off) {
+                                       unsigned int blocks =
+                                               (PAGE_SIZE - off) >> bsize_shift;
+
+                                       bio = gfs2_chain_bio(bio, blocks);
+                                       bio_chained = true;
+                                       goto add_block_to_new_bio;
                                }
+                       }
+
+                       if (bio) {
                                blocks_submitted = block + 1;
                                submit_bio(bio);
-                               bio = NULL;
                        }
 
-                       dblock = je->dblock + (block - je->lblock);
                        bio = gfs2_log_alloc_bio(sdp, dblock, gfs2_end_log_read);
                        bio->bi_opf = REQ_OP_READ;
-                       sz = bio_add_page(bio, page, bsize, 0);
-                       gfs2_assert_warn(sdp, sz == bsize);
-                       if (bsize == PAGE_SIZE)
+                       bio_chained = false;
+add_block_to_new_bio:
+                       sz = bio_add_page(bio, page, bsize, off);
+                       BUG_ON(sz != bsize);
+block_added:
+                       off += bsize;
+                       if (off == PAGE_SIZE)
                                page = NULL;
-
-page_added:
-                       if (blocks_submitted < blocks_read + readhead_blocks) {
+                       if (blocks_submitted < blocks_read + readahead_blocks) {
                                /* Keep at least one bio in flight */
                                continue;
                        }