btrfs: make read verification handle bs > ps cases without large folios

The current read verification is also relying on large folios to support
bs > ps cases, but that introduced quite some limits.

To enhance read-repair to support bs > ps without large folios:

- Make btrfs_data_csum_ok() to accept an array of paddrs
  Which can pass the paddrs[] direct into
  btrfs_calculate_block_csum_pages().

- Make repair_one_sector() to accept an array of paddrs
  So that it can submit a repair bio backed by regular pages, not only
  large folios.
  This requires us to allocate more slots at bio allocation time though.

  Also since the caller may have only partially advanced the saved_iter
  for bs > ps cases, we can not directly trust the logical bytenr from
  saved_iter (can be unaligned), thus a manual round down is necessary
  for the logical bytenr.

- Make btrfs_check_read_bio() to build an array of paddrs
  The tricky part is that we can only call btrfs_data_csum_ok() after
  all involved pages are assembled.

  This means at the call time of btrfs_check_read_bio(), our offset
  inside the bio is already at the end of the fs block.
  Thus we must re-calculate @bio_offset for btrfs_data_csum_ok() and
  repair_one_sector().

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
pull/1354/merge
Qu Wenruo 2025-11-11 09:12:00 +10:30 committed by David Sterba
parent 2574e90110
commit 052fd7a5ca
3 changed files with 52 additions and 30 deletions

View File

@ -171,7 +171,6 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
struct btrfs_failed_bio *fbio = repair_bbio->private;
struct btrfs_inode *inode = repair_bbio->inode;
struct btrfs_fs_info *fs_info = inode->root->fs_info;
struct bio_vec *bv = bio_first_bvec_all(&repair_bbio->bio);
/*
* We can not move forward the saved_iter, as it will be later
* utilized by repair_bbio again.
@ -188,8 +187,14 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
/* Repair bbio should be eaxctly one block sized. */
ASSERT(repair_bbio->saved_iter.bi_size == fs_info->sectorsize);
btrfs_bio_for_each_block(paddr, &repair_bbio->bio, &saved_iter, step) {
ASSERT(slot < nr_steps);
paddrs[slot] = paddr;
slot++;
}
if (repair_bbio->bio.bi_status ||
!btrfs_data_csum_ok(repair_bbio, dev, 0, bvec_phys(bv))) {
!btrfs_data_csum_ok(repair_bbio, dev, 0, paddrs)) {
bio_reset(&repair_bbio->bio, NULL, REQ_OP_READ);
repair_bbio->bio.bi_iter = repair_bbio->saved_iter;
@ -204,12 +209,6 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
return;
}
btrfs_bio_for_each_block(paddr, &repair_bbio->bio, &saved_iter, step) {
ASSERT(slot < nr_steps);
paddrs[slot] = paddr;
slot++;
}
do {
mirror = prev_repair_mirror(fbio, mirror);
btrfs_repair_io_failure(fs_info, btrfs_ino(inode),
@ -231,21 +230,25 @@ done:
*/
static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
u32 bio_offset,
phys_addr_t paddr,
phys_addr_t paddrs[],
struct btrfs_failed_bio *fbio)
{
struct btrfs_inode *inode = failed_bbio->inode;
struct btrfs_fs_info *fs_info = inode->root->fs_info;
struct folio *folio = page_folio(phys_to_page(paddr));
const u32 sectorsize = fs_info->sectorsize;
const u32 foff = offset_in_folio(folio, paddr);
const u64 logical = (failed_bbio->saved_iter.bi_sector << SECTOR_SHIFT);
const u32 step = min(fs_info->sectorsize, PAGE_SIZE);
const u32 nr_steps = sectorsize / step;
/*
* For bs > ps cases, the saved_iter can be partially moved forward.
* In that case we should round it down to the block boundary.
*/
const u64 logical = round_down(failed_bbio->saved_iter.bi_sector << SECTOR_SHIFT,
sectorsize);
struct btrfs_bio *repair_bbio;
struct bio *repair_bio;
int num_copies;
int mirror;
ASSERT(foff + sectorsize <= folio_size(folio));
btrfs_debug(fs_info, "repair read error: read error at %llu",
failed_bbio->file_offset + bio_offset);
@ -265,10 +268,18 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
atomic_inc(&fbio->repair_count);
repair_bio = bio_alloc_bioset(NULL, 1, REQ_OP_READ, GFP_NOFS,
repair_bio = bio_alloc_bioset(NULL, nr_steps, REQ_OP_READ, GFP_NOFS,
&btrfs_repair_bioset);
repair_bio->bi_iter.bi_sector = failed_bbio->saved_iter.bi_sector;
bio_add_folio_nofail(repair_bio, folio, sectorsize, foff);
repair_bio->bi_iter.bi_sector = logical >> SECTOR_SHIFT;
for (int i = 0; i < nr_steps; i++) {
int ret;
ASSERT(offset_in_page(paddrs[i]) + step <= PAGE_SIZE);
ret = bio_add_page(repair_bio, phys_to_page(paddrs[i]), step,
offset_in_page(paddrs[i]));
ASSERT(ret == step);
}
repair_bbio = btrfs_bio(repair_bio);
btrfs_bio_init(repair_bbio, failed_bbio->inode, failed_bbio->file_offset + bio_offset,
@ -284,10 +295,13 @@ static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *de
{
struct btrfs_inode *inode = bbio->inode;
struct btrfs_fs_info *fs_info = inode->root->fs_info;
u32 sectorsize = fs_info->sectorsize;
const u32 sectorsize = fs_info->sectorsize;
const u32 step = min(sectorsize, PAGE_SIZE);
const u32 nr_steps = sectorsize / step;
struct bvec_iter *iter = &bbio->saved_iter;
blk_status_t status = bbio->bio.bi_status;
struct btrfs_failed_bio *fbio = NULL;
phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE];
phys_addr_t paddr;
u32 offset = 0;
@ -306,10 +320,16 @@ static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *de
/* Clear the I/O error. A failed repair will reset it. */
bbio->bio.bi_status = BLK_STS_OK;
btrfs_bio_for_each_block(paddr, &bbio->bio, iter, fs_info->sectorsize) {
if (status || !btrfs_data_csum_ok(bbio, dev, offset, paddr))
fbio = repair_one_sector(bbio, offset, paddr, fbio);
offset += sectorsize;
btrfs_bio_for_each_block(paddr, &bbio->bio, iter, step) {
paddrs[(offset / step) % nr_steps] = paddr;
offset += step;
if (IS_ALIGNED(offset, sectorsize)) {
if (status ||
!btrfs_data_csum_ok(bbio, dev, offset - sectorsize, paddrs))
fbio = repair_one_sector(bbio, offset - sectorsize,
paddrs, fbio);
}
}
if (bbio->csum != bbio->csum_inline)
kvfree(bbio->csum);

View File

@ -550,7 +550,7 @@ void btrfs_calculate_block_csum_pages(struct btrfs_fs_info *fs_info,
int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8 *csum,
const u8 * const csum_expected);
bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev,
u32 bio_offset, phys_addr_t paddr);
u32 bio_offset, const phys_addr_t paddrs[]);
noinline int can_nocow_extent(struct btrfs_inode *inode, u64 offset, u64 *len,
struct btrfs_file_extent *file_extent,
bool nowait);

View File

@ -3420,12 +3420,13 @@ int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8
}
/*
* Verify the checksum of a single data sector.
* Verify the checksum of a single data sector, which can be scattered at
* different noncontiguous pages.
*
* @bbio: btrfs_io_bio which contains the csum
* @dev: device the sector is on
* @bio_offset: offset to the beginning of the bio (in bytes)
* @bv: bio_vec to check
* @paddrs: physical addresses which back the fs block
*
* Check if the checksum on a data block is valid. When a checksum mismatch is
* detected, report the error and fill the corrupted range with zero.
@ -3433,12 +3434,13 @@ int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8
* Return %true if the sector is ok or had no checksum to start with, else %false.
*/
bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev,
u32 bio_offset, phys_addr_t paddr)
u32 bio_offset, const phys_addr_t paddrs[])
{
struct btrfs_inode *inode = bbio->inode;
struct btrfs_fs_info *fs_info = inode->root->fs_info;
const u32 blocksize = fs_info->sectorsize;
struct folio *folio;
const u32 step = min(blocksize, PAGE_SIZE);
const u32 nr_steps = blocksize / step;
u64 file_offset = bbio->file_offset + bio_offset;
u64 end = file_offset + blocksize - 1;
u8 *csum_expected;
@ -3458,7 +3460,8 @@ bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev,
csum_expected = bbio->csum + (bio_offset >> fs_info->sectorsize_bits) *
fs_info->csum_size;
if (btrfs_check_block_csum(fs_info, paddr, csum, csum_expected))
btrfs_calculate_block_csum_pages(fs_info, paddrs, csum);
if (unlikely(memcmp(csum, csum_expected, fs_info->csum_size) != 0))
goto zeroit;
return true;
@ -3467,9 +3470,8 @@ zeroit:
bbio->mirror_num);
if (dev)
btrfs_dev_stat_inc_and_print(dev, BTRFS_DEV_STAT_CORRUPTION_ERRS);
folio = page_folio(phys_to_page(paddr));
ASSERT(offset_in_folio(folio, paddr) + blocksize <= folio_size(folio));
folio_zero_range(folio, offset_in_folio(folio, paddr), blocksize);
for (int i = 0; i < nr_steps; i++)
memzero_page(phys_to_page(paddrs[i]), offset_in_page(paddrs[i]), step);
return false;
}