Skip to content

Commit 27bf6a6

Browse files
hexiaole1994Jaegeuk Kim
authored andcommitted
f2fs: fix age extent cache insertion skip on counter overflow
The age extent cache uses last_blocks (derived from allocated_data_blocks) to determine data age. However, there's a conflict between the deletion marker (last_blocks=0) and legitimate last_blocks=0 cases when allocated_data_blocks overflows to 0 after reaching ULLONG_MAX. In this case, valid extents are incorrectly skipped due to the "if (!tei->last_blocks)" check in __update_extent_tree_range(). This patch fixes the issue by: 1. Reserving ULLONG_MAX as an invalid/deletion marker 2. Limiting allocated_data_blocks to range [0, ULLONG_MAX-1] 3. Using F2FS_EXTENT_AGE_INVALID for deletion scenarios 4. Adjusting overflow age calculation from ULLONG_MAX to (ULLONG_MAX-1) Reproducer (using a patched kernel with allocated_data_blocks initialized to ULLONG_MAX - 3 for quick testing): Step 1: Mount and check initial state # dd if=/dev/zero of=/tmp/test.img bs=1M count=100 # mkfs.f2fs -f /tmp/test.img # mkdir -p /mnt/f2fs_test # mount -t f2fs -o loop,age_extent_cache /tmp/test.img /mnt/f2fs_test # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 18446744073709551612 # ULLONG_MAX - 3 Inner Struct Count: tree: 1(0), node: 0 Step 2: Create files and write data to trigger overflow # touch /mnt/f2fs_test/{1,2,3,4}.txt; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 18446744073709551613 # ULLONG_MAX - 2 Inner Struct Count: tree: 5(0), node: 1 # dd if=/dev/urandom of=/mnt/f2fs_test/1.txt bs=4K count=1; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 18446744073709551614 # ULLONG_MAX - 1 Inner Struct Count: tree: 5(0), node: 2 # dd if=/dev/urandom of=/mnt/f2fs_test/2.txt bs=4K count=1; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 18446744073709551615 # ULLONG_MAX Inner Struct Count: tree: 5(0), node: 3 # dd if=/dev/urandom of=/mnt/f2fs_test/3.txt bs=4K count=1; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 0 # Counter overflowed! Inner Struct Count: tree: 5(0), node: 4 Step 3: Trigger the bug - next write should create node but gets skipped # dd if=/dev/urandom of=/mnt/f2fs_test/4.txt bs=4K count=1; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 1 Inner Struct Count: tree: 5(0), node: 4 Expected: node: 5 (new extent node for 4.txt) Actual: node: 4 (extent insertion was incorrectly skipped due to last_blocks = allocated_data_blocks = 0 in __get_new_block_age) After this fix, the extent node is correctly inserted and node count becomes 5 as expected. Fixes: 71644df ("f2fs: add block_age-based extent cache") Cc: stable@kernel.org Signed-off-by: Xiaole He <hexiaole1994@126.com> Reviewed-by: Chao Yu <chao@kernel.org> Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
1 parent f37981e commit 27bf6a6

File tree

3 files changed

+16
-4
lines changed

3 files changed

+16
-4
lines changed

fs/f2fs/extent_cache.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ static void __update_extent_tree_range(struct inode *inode,
808808
}
809809
goto out_read_extent_cache;
810810
update_age_extent_cache:
811-
if (!tei->last_blocks)
811+
if (tei->last_blocks == F2FS_EXTENT_AGE_INVALID)
812812
goto out_read_extent_cache;
813813

814814
__set_extent_info(&ei, fofs, len, 0, false,
@@ -912,7 +912,7 @@ static int __get_new_block_age(struct inode *inode, struct extent_info *ei,
912912
cur_age = cur_blocks - tei.last_blocks;
913913
else
914914
/* allocated_data_blocks overflow */
915-
cur_age = ULLONG_MAX - tei.last_blocks + cur_blocks;
915+
cur_age = (ULLONG_MAX - 1) - tei.last_blocks + cur_blocks;
916916

917917
if (tei.age)
918918
ei->age = __calculate_block_age(sbi, cur_age, tei.age);
@@ -1114,6 +1114,7 @@ void f2fs_update_age_extent_cache_range(struct dnode_of_data *dn,
11141114
struct extent_info ei = {
11151115
.fofs = fofs,
11161116
.len = len,
1117+
.last_blocks = F2FS_EXTENT_AGE_INVALID,
11171118
};
11181119

11191120
if (!__may_extent_tree(dn->inode, EX_BLOCK_AGE))

fs/f2fs/f2fs.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,12 @@ enum extent_type {
707707
NR_EXTENT_CACHES,
708708
};
709709

710+
/*
711+
* Reserved value to mark invalid age extents, hence valid block range
712+
* from 0 to ULLONG_MAX-1
713+
*/
714+
#define F2FS_EXTENT_AGE_INVALID ULLONG_MAX
715+
710716
struct extent_info {
711717
unsigned int fofs; /* start offset in a file */
712718
unsigned int len; /* length of the extent */

fs/f2fs/segment.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3863,8 +3863,13 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio,
38633863
locate_dirty_segment(sbi, GET_SEGNO(sbi, old_blkaddr));
38643864
locate_dirty_segment(sbi, GET_SEGNO(sbi, *new_blkaddr));
38653865

3866-
if (IS_DATASEG(curseg->seg_type))
3867-
atomic64_inc(&sbi->allocated_data_blocks);
3866+
if (IS_DATASEG(curseg->seg_type)) {
3867+
unsigned long long new_val;
3868+
3869+
new_val = atomic64_inc_return(&sbi->allocated_data_blocks);
3870+
if (unlikely(new_val == ULLONG_MAX))
3871+
atomic64_set(&sbi->allocated_data_blocks, 0);
3872+
}
38683873

38693874
up_write(&sit_i->sentry_lock);
38703875

0 commit comments

Comments
 (0)