mirror of
https://github.com/torvalds/linux.git
synced 2025-01-01 07:42:07 +00:00
gfs2: Another gfs2_find_jhead fix
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: f4686c26ec
("gfs2: read journal in large chunks")
Cc: stable@vger.kernel.org # v5.2+
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
This commit is contained in:
parent
887352fb5f
commit
eed0f953b9
@ -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 super_block *sb = sdp->sd_vfs;
|
||||||
struct bio *bio = bio_alloc(GFP_NOIO, BIO_MAX_PAGES);
|
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_set_dev(bio, sb->s_bdev);
|
||||||
bio->bi_end_io = end_io;
|
bio->bi_end_io = end_io;
|
||||||
bio->bi_private = sdp;
|
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 */
|
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
|
* gfs2_find_jhead - find the head of a log
|
||||||
* @jd: The journal descriptor
|
* @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 gfs2_sbd *sdp = GFS2_SB(jd->jd_inode);
|
||||||
struct address_space *mapping = jd->jd_inode->i_mapping;
|
struct address_space *mapping = jd->jd_inode->i_mapping;
|
||||||
unsigned int block = 0, blocks_submitted = 0, blocks_read = 0;
|
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 bsize_shift = sdp->sd_sb.sb_bsize_shift;
|
||||||
unsigned int shift = PAGE_SHIFT - 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;
|
struct gfs2_journal_extent *je;
|
||||||
int sz, ret = 0;
|
int sz, ret = 0;
|
||||||
struct bio *bio = NULL;
|
struct bio *bio = NULL;
|
||||||
struct page *page = NULL;
|
struct page *page = NULL;
|
||||||
bool done = false;
|
bool bio_chained = false, done = false;
|
||||||
errseq_t since;
|
errseq_t since;
|
||||||
|
|
||||||
memset(head, 0, sizeof(*head));
|
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);
|
since = filemap_sample_wb_err(mapping);
|
||||||
list_for_each_entry(je, &jd->extent_list, list) {
|
list_for_each_entry(je, &jd->extent_list, list) {
|
||||||
for (; block < je->lblock + je->blocks; block++) {
|
u64 dblock = je->dblock;
|
||||||
u64 dblock;
|
|
||||||
|
|
||||||
|
for (; block < je->lblock + je->blocks; block++, dblock++) {
|
||||||
if (!page) {
|
if (!page) {
|
||||||
page = find_or_create_page(mapping,
|
page = find_or_create_page(mapping,
|
||||||
block >> shift, GFP_NOFS);
|
block >> shift, GFP_NOFS);
|
||||||
@ -516,35 +530,41 @@ int gfs2_find_jhead(struct gfs2_jdesc *jd, struct gfs2_log_header_host *head,
|
|||||||
done = true;
|
done = true;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
off = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bio || (bio_chained && !off)) {
|
||||||
|
/* start new bio */
|
||||||
|
} else {
|
||||||
|
sz = bio_add_page(bio, page, bsize, off);
|
||||||
|
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) {
|
if (bio) {
|
||||||
unsigned int off;
|
|
||||||
|
|
||||||
off = (block << bsize_shift) & ~PAGE_MASK;
|
|
||||||
sz = bio_add_page(bio, page, bsize, off);
|
|
||||||
if (sz == bsize) { /* block added */
|
|
||||||
if (off + bsize == PAGE_SIZE) {
|
|
||||||
page = NULL;
|
|
||||||
goto page_added;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
blocks_submitted = block + 1;
|
blocks_submitted = block + 1;
|
||||||
submit_bio(bio);
|
submit_bio(bio);
|
||||||
bio = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dblock = je->dblock + (block - je->lblock);
|
|
||||||
bio = gfs2_log_alloc_bio(sdp, dblock, gfs2_end_log_read);
|
bio = gfs2_log_alloc_bio(sdp, dblock, gfs2_end_log_read);
|
||||||
bio->bi_opf = REQ_OP_READ;
|
bio->bi_opf = REQ_OP_READ;
|
||||||
sz = bio_add_page(bio, page, bsize, 0);
|
bio_chained = false;
|
||||||
gfs2_assert_warn(sdp, sz == bsize);
|
add_block_to_new_bio:
|
||||||
if (bsize == PAGE_SIZE)
|
sz = bio_add_page(bio, page, bsize, off);
|
||||||
|
BUG_ON(sz != bsize);
|
||||||
|
block_added:
|
||||||
|
off += bsize;
|
||||||
|
if (off == PAGE_SIZE)
|
||||||
page = NULL;
|
page = NULL;
|
||||||
|
if (blocks_submitted < blocks_read + readahead_blocks) {
|
||||||
page_added:
|
|
||||||
if (blocks_submitted < blocks_read + readhead_blocks) {
|
|
||||||
/* Keep at least one bio in flight */
|
/* Keep at least one bio in flight */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user