[PATCH 2/2] f2fs: refresh pinned allocation boundary after resize

From: Wenjie Qi

Date: Fri Aug 28 2026 - 10:18:00 EST


pinned_area_max_secno is derived from MAIN_SECS(), the zoned device
boundary, and resizable_tail_secno. It is currently recalculated only
at mount and remount, so online resize leaves the pre-resize value
active after MAIN_SECS() changes.

A stale value can allow pinned allocations in sections outside the
new main-area boundary.

Move the calculation to segment.h and call it whenever
update_fs_metadata() changes MAIN_SECS(). This also restores the
previous boundary when a final checkpoint error rolls the resize
metadata back.

Mount option validation compares the tail count with the pre-resize
section count. Recalculating after a shrink below that tail would make
the unsigned subtraction wrap. Reject such a shrink with -EINVAL
before taking resize locks or changing filesystem geometry.

Fixes: c966d29e01bb ("f2fs: support resizable tail section and unify pinned allocation")
Signed-off-by: Wenjie Qi <qiwenjie@xxxxxxxxxx>
---
fs/f2fs/gc.c | 7 +++++++
fs/f2fs/segment.h | 16 ++++++++++++++++
fs/f2fs/super.c | 15 ++-------------
3 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
index 0a00180c21dc..978e85578c79 100644
--- a/fs/f2fs/gc.c
+++ b/fs/f2fs/gc.c
@@ -2326,6 +2326,7 @@ static void update_fs_metadata(struct f2fs_sb_info *sbi, int secs)
SM_I(sbi)->segment_count = (int)SM_I(sbi)->segment_count + segs;
MAIN_SEGS(sbi) = (int)MAIN_SEGS(sbi) + segs;
MAIN_SECS(sbi) += secs;
+ f2fs_adjust_pinned_area_boundary(sbi);
if (sbi->allocate_section_hint > MAIN_SECS(sbi))
sbi->allocate_section_hint = MAIN_SECS(sbi);
FREE_I(sbi)->free_sections = (int)FREE_I(sbi)->free_sections + secs;
@@ -2398,6 +2399,12 @@ int f2fs_resize_fs(struct file *filp, __u64 block_count)

shrunk_blocks = old_block_count - block_count;
secs = div_u64(shrunk_blocks, BLKS_PER_SEC(sbi));
+ if (secs >= MAIN_SECS(sbi) ||
+ F2FS_OPTION(sbi).resizable_tail_secno >=
+ MAIN_SECS(sbi) - secs) {
+ err = -EINVAL;
+ goto out_drop_write;
+ }

/* stop other GC */
if (!f2fs_down_write_trylock_trace(&sbi->gc_lock, &glc)) {
diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h
index 5949aa5200ac..7c7ca0730d77 100644
--- a/fs/f2fs/segment.h
+++ b/fs/f2fs/segment.h
@@ -91,6 +91,22 @@ static inline void sanity_check_seg_type(struct f2fs_sb_info *sbi,
#define GET_ZONE_FROM_SEG(sbi, segno) \
GET_ZONE_FROM_SEC(sbi, GET_SEC_FROM_SEG(sbi, segno))

+static inline void f2fs_adjust_pinned_area_boundary(struct f2fs_sb_info *sbi)
+{
+ sbi->pinned_area_max_secno = MAIN_SECS(sbi);
+ if (f2fs_sb_has_blkzoned(sbi) &&
+ sbi->first_seq_zone_segno != NULL_SEGNO)
+ sbi->pinned_area_max_secno =
+ min(sbi->pinned_area_max_secno,
+ GET_SEC_FROM_SEG(sbi,
+ sbi->first_seq_zone_segno));
+ if (F2FS_OPTION(sbi).resizable_tail_secno)
+ sbi->pinned_area_max_secno =
+ min(sbi->pinned_area_max_secno,
+ MAIN_SECS(sbi) -
+ F2FS_OPTION(sbi).resizable_tail_secno);
+}
+
#define GET_SUM_BLOCK(sbi, segno) \
(SM_I(sbi)->ssa_blkaddr + (segno / (sbi)->sums_per_block))
#define GET_SUM_BLKOFF(sbi, segno) (segno % (sbi)->sums_per_block)
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 253a579e9d5b..c8f8ecf49635 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -554,17 +554,6 @@ static inline void adjust_unusable_cap_perc(struct f2fs_sb_info *sbi)
F2FS_OPTION(sbi).unusable_cap_perc);
}

-static inline void adjust_pinned_area_boundary(struct f2fs_sb_info *sbi)
-{
- sbi->pinned_area_max_secno = MAIN_SECS(sbi);
- if (f2fs_sb_has_blkzoned(sbi) && sbi->first_seq_zone_segno != NULL_SEGNO)
- sbi->pinned_area_max_secno = min(sbi->pinned_area_max_secno,
- GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno));
- if (F2FS_OPTION(sbi).resizable_tail_secno)
- sbi->pinned_area_max_secno = min(sbi->pinned_area_max_secno,
- MAIN_SECS(sbi) - F2FS_OPTION(sbi).resizable_tail_secno);
-}
-
static void init_once(void *foo)
{
struct f2fs_inode_info *fi = (struct f2fs_inode_info *) foo;
@@ -3078,7 +3067,7 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb)
sb->s_flags = (sb->s_flags & ~SB_POSIXACL) |
(test_opt(sbi, POSIX_ACL) ? SB_POSIXACL : 0);

- adjust_pinned_area_boundary(sbi);
+ f2fs_adjust_pinned_area_boundary(sbi);
limit_reserve_root(sbi);
fc->sb_flags = (flags & ~SB_LAZYTIME) | (sb->s_flags & SB_LAZYTIME);

@@ -5321,7 +5310,7 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
/* get segno of first zoned block device */
sbi->first_seq_zone_segno = get_first_seq_zone_segno(sbi);

- adjust_pinned_area_boundary(sbi);
+ f2fs_adjust_pinned_area_boundary(sbi);

sbi->reserved_pin_section = f2fs_sb_has_blkzoned(sbi) ?
ZONED_PIN_SEC_REQUIRED_COUNT :
--
2.43.0