[PATCH] f2fs: fix to keep m_last_pblk across direct write mappings
From: Keoseong Park
Date: Mon Oct 26 2026 - 00:35:52 EST
Since commit f0a7adfedcc8 ("f2fs: don't break allocation when crossing
contiguous sections"), a direct write in LFS mode that allocates a
block which is not contiguous with the current mapping ends the mapping
and remembers the block in m_last_pblk, so that the next mapping starts
with it instead of allocating again.
The value is carried over in iomap->private, but iomap clears the iomap
between two mappings, so the next call never sees it. It allocates a
second block and the first one is left unwritten in the log.
Keep the value in the private data of the iterator instead, which
lives as long as the write.
alloc_mode=reuse below is only used to get a non-contiguous section
with a single writer on a fresh filesystem. Concurrent direct writers
lose blocks the same way with any alloc_mode.
Testcase:
- mkfs.f2fs -f /dev/vda
- mount -o mode=lfs,alloc_mode=reuse /dev/vda /mnt/f2fs
- for i in 0 1 2 3 4 5 6 7 8 9; do
dd if=/dev/zero of=/mnt/f2fs/k$i bs=2M count=1 conv=fsync; done
- rm /mnt/f2fs/k1 /mnt/f2fs/k3 /mnt/f2fs/k5 /mnt/f2fs/k7; sync
- echo 1 > /sys/kernel/tracing/events/f2fs/f2fs_map_blocks/enable
- dd if=/dev/zero of=/mnt/f2fs/dio bs=8M count=1 oflag=direct; sync
- cat /sys/kernel/tracing/trace
- cat /sys/fs/f2fs/vda/dirty_segments
Before, the f2fs_map_blocks trace events of the direct write show that
the first block of each section it moves on to is lost, and
dirty_segments reads 3:
file offset = 512, start blkaddr = 0x1c01, len = 0x1ff
file offset = 1023, start blkaddr = 0x2001, len = 0x1ff
file offset = 1534, start blkaddr = 0x2401, len = 0x1ff
After, no block is lost and dirty_segments reads 0:
file offset = 512, start blkaddr = 0x1c00, len = 0x200
file offset = 1024, start blkaddr = 0x2000, len = 0x200
file offset = 1536, start blkaddr = 0x2400, len = 0x200
Fixes: f0a7adfedcc8 ("f2fs: don't break allocation when crossing contiguous sections")
Signed-off-by: Keoseong Park <keosung.park@xxxxxxxxxxx>
---
fs/f2fs/data.c | 14 +++++++++-----
fs/f2fs/f2fs.h | 5 +++++
fs/f2fs/file.c | 6 ++++--
3 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 6d4ba5e7790..5c32a77f2d0 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -4756,18 +4756,22 @@ static int f2fs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
{
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct f2fs_map_blocks map = { NULL, };
+ struct f2fs_dio_write *dio_write = NULL;
pgoff_t next_pgofs = 0;
int err;
+ if (flags & IOMAP_WRITE)
+ dio_write = container_of(iomap, struct iomap_iter, iomap)->private;
+
map.m_lblk = F2FS_BYTES_TO_BLK(sbi, offset);
map.m_len = F2FS_BYTES_TO_BLK(sbi, offset + length - 1) -
map.m_lblk + 1;
map.m_next_pgofs = &next_pgofs;
map.m_seg_type = f2fs_rw_hint_to_seg_type(F2FS_I_SB(inode),
inode->i_write_hint);
- if (flags & IOMAP_WRITE && iomap->private) {
- map.m_last_pblk = (unsigned long)iomap->private;
- iomap->private = NULL;
+ if (dio_write) {
+ map.m_last_pblk = dio_write->last_pblk;
+ dio_write->last_pblk = NULL_ADDR;
}
/*
@@ -4808,8 +4812,8 @@ static int f2fs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
iomap->bdev = map.m_bdev;
iomap->addr = F2FS_BLK_TO_BYTES(sbi, map.m_pblk);
- if (flags & IOMAP_WRITE && map.m_last_pblk)
- iomap->private = (void *)map.m_last_pblk;
+ if (dio_write)
+ dio_write->last_pblk = map.m_last_pblk;
} else {
if (flags & IOMAP_WRITE)
return -ENOTBLK;
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 089a62c054e..2a11b14f473 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -829,6 +829,11 @@ struct extent_tree_info {
#define F2FS_MAP_FLAGS (F2FS_MAP_NEW | F2FS_MAP_MAPPED |\
F2FS_MAP_DELALLOC)
+struct f2fs_dio_write {
+ struct kiocb *iocb;
+ block_t last_pblk; /* block allocated ahead for the next mapping */
+};
+
struct f2fs_map_blocks {
struct block_device *m_bdev; /* for multi-device dio */
block_t m_pblk;
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index ef4d218e694..86bd1692011 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -5488,7 +5488,8 @@ static void f2fs_dio_write_submit_io(const struct iomap_iter *iter,
{
struct inode *inode = iter->inode;
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
- struct kiocb *iocb = iter->private;
+ struct f2fs_dio_write *dio_write = iter->private;
+ struct kiocb *iocb = dio_write->iocb;
enum log_type type = f2fs_rw_hint_to_seg_type(sbi, inode->i_write_hint);
enum temp_type temp = f2fs_get_segment_temp(sbi, type);
@@ -5526,6 +5527,7 @@ static ssize_t f2fs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from,
struct f2fs_inode_info *fi = F2FS_I(inode);
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
const bool do_opu = f2fs_lfs_mode(sbi);
+ struct f2fs_dio_write dio_write = { .iocb = iocb };
const loff_t pos = iocb->ki_pos;
const ssize_t count = iov_iter_count(from);
unsigned int dio_flags;
@@ -5576,7 +5578,7 @@ static ssize_t f2fs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from,
if (pos + count > inode->i_size)
dio_flags |= IOMAP_DIO_FORCE_WAIT;
dio = __iomap_dio_rw(iocb, from, &f2fs_iomap_ops,
- &f2fs_iomap_dio_write_ops, dio_flags, iocb, 0);
+ &f2fs_iomap_dio_write_ops, dio_flags, &dio_write, 0);
if (IS_ERR_OR_NULL(dio)) {
ret = PTR_ERR_OR_ZERO(dio);
if (ret == -ENOTBLK)
--
2.43.0