Merge patch series "iomap: trivial fixes for ext4 conversion"

Zhang Yi <yi.zhang@huaweicloud.com> says:

This patch series contains a few trivial iomap-related fixes in
preparation for converting ext4 buffered I/O to use iomap.

The first three patches are taken from my ext4 conversion series [1], as
suggested by Christoph. The fourth patch fixes a bug originally reported
by Sashiko during review of my series; although unrelated to the ext4
conversion, it is worth fixing on its own. Please see the following
patches for detail. The fifth patch add comments for
ifs_clear/set_range_dirty(), and the last patch avoids merging ioends
that have different private data.

[1] https://lore.kernel.org/linux-ext4/20260511072344.191271-1-yi.zhang@huaweicloud.com/

* patches from https://patch.msgid.link/20260714082325.325163-1-yi.zhang@huaweicloud.com:
  iomap: add comments for ifs_clear/set_range_dirty()
  iomap: fix out-of-bounds bitmap_set() with zero-length range
  iomap: fix incorrect did_zero setting in iomap_zero_iter()
  iomap: support invalidating partial folios
  iomap: correct the range of a partial dirty clear

Link: https://patch.msgid.link/20260714082325.325163-1-yi.zhang@huaweicloud.com
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-07-23 11:26:28 +02:00
commit 1c9cb1826b

View File

@ -68,11 +68,13 @@ static bool ifs_set_range_uptodate(struct folio *folio,
struct iomap_folio_state *ifs, size_t off, size_t len)
{
struct inode *inode = folio->mapping->host;
unsigned int first_blk = off >> inode->i_blkbits;
unsigned int last_blk = (off + len - 1) >> inode->i_blkbits;
unsigned int nr_blks = last_blk - first_blk + 1;
unsigned int first_blk, last_blk;
bitmap_set(ifs->state, first_blk, nr_blks);
if (len) {
first_blk = off >> inode->i_blkbits;
last_blk = (off + len - 1) >> inode->i_blkbits;
bitmap_set(ifs->state, first_blk, last_blk - first_blk + 1);
}
return ifs_is_fully_uptodate(folio, ifs);
}
@ -172,18 +174,29 @@ static unsigned iomap_find_dirty_range(struct folio *folio, u64 *range_start,
return range_end - *range_start;
}
/*
* Clear the per-block dirty bits for the range [@off, @off + @len) within a
* folio. The range is rounded inwards so that only blocks fully covered by
* the range are cleared. This is required for operations like folio
* invalidation, where we must ensure a block is fully clean before discarding
* it.
*/
static void ifs_clear_range_dirty(struct folio *folio,
struct iomap_folio_state *ifs, size_t off, size_t len)
{
struct inode *inode = folio->mapping->host;
unsigned int blks_per_folio = i_blocks_per_folio(inode, folio);
unsigned int first_blk = (off >> inode->i_blkbits);
unsigned int last_blk = (off + len - 1) >> inode->i_blkbits;
unsigned int nr_blks = last_blk - first_blk + 1;
unsigned int first_blk = round_up(off, i_blocksize(inode)) >>
inode->i_blkbits;
unsigned int last_blk = (off + len) >> inode->i_blkbits;
unsigned long flags;
if (first_blk >= last_blk)
return;
spin_lock_irqsave(&ifs->state_lock, flags);
bitmap_clear(ifs->state, first_blk + blks_per_folio, nr_blks);
bitmap_clear(ifs->state, first_blk + blks_per_folio,
last_blk - first_blk);
spin_unlock_irqrestore(&ifs->state_lock, flags);
}
@ -195,18 +208,29 @@ static void iomap_clear_range_dirty(struct folio *folio, size_t off, size_t len)
ifs_clear_range_dirty(folio, ifs, off, len);
}
/*
* Set the per-block dirty bits for the range [@off, @off + @len) within a
* folio. The range is rounded outwards so that any block partially touched
* by the range is marked dirty. This ensures blocks containing even a
* single dirty byte will be included in subsequent writeback, preventing
* data loss when partial blocks are written.
*/
static void ifs_set_range_dirty(struct folio *folio,
struct iomap_folio_state *ifs, size_t off, size_t len)
{
struct inode *inode = folio->mapping->host;
unsigned int blks_per_folio = i_blocks_per_folio(inode, folio);
unsigned int first_blk = (off >> inode->i_blkbits);
unsigned int last_blk = (off + len - 1) >> inode->i_blkbits;
unsigned int nr_blks = last_blk - first_blk + 1;
unsigned int first_blk, last_blk;
unsigned long flags;
if (!len)
return;
first_blk = off >> inode->i_blkbits;
last_blk = (off + len - 1) >> inode->i_blkbits;
spin_lock_irqsave(&ifs->state_lock, flags);
bitmap_set(ifs->state, first_blk + blks_per_folio, nr_blks);
bitmap_set(ifs->state, first_blk + blks_per_folio,
last_blk - first_blk + 1);
spin_unlock_irqrestore(&ifs->state_lock, flags);
}
@ -811,6 +835,8 @@ void iomap_invalidate_folio(struct folio *folio, size_t offset, size_t len)
WARN_ON_ONCE(folio_test_writeback(folio));
folio_cancel_dirty(folio);
ifs_free(folio);
} else {
iomap_clear_range_dirty(folio, offset, len);
}
}
EXPORT_SYMBOL_GPL(iomap_invalidate_folio);
@ -1619,6 +1645,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
const struct iomap_write_ops *write_ops)
{
u64 bytes = iomap_length(iter);
bool zeroed = false;
int status;
do {
@ -1639,6 +1666,8 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
/* a NULL folio means we're done with a folio batch */
if (!folio) {
status = iomap_iter_advance_full(iter);
if (status)
return status;
break;
}
@ -1649,6 +1678,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
bytes);
folio_zero_range(folio, offset, bytes);
zeroed = true;
folio_mark_accessed(folio);
ret = iomap_write_end(iter, bytes, bytes, folio);
@ -1658,10 +1688,10 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
status = iomap_iter_advance(iter, bytes);
if (status)
break;
return status;
} while ((bytes = iomap_length(iter)) > 0);
if (did_zero)
if (did_zero && zeroed)
*did_zero = true;
return status;
}