Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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: 71644dff4811 ("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>

authored by

Xiaole He and committed by
Jaegeuk Kim
27bf6a63 f37981ed

+16 -4
+3 -2
fs/f2fs/extent_cache.c
··· 808 808 } 809 809 goto out_read_extent_cache; 810 810 update_age_extent_cache: 811 - if (!tei->last_blocks) 811 + if (tei->last_blocks == F2FS_EXTENT_AGE_INVALID) 812 812 goto out_read_extent_cache; 813 813 814 814 __set_extent_info(&ei, fofs, len, 0, false, ··· 912 912 cur_age = cur_blocks - tei.last_blocks; 913 913 else 914 914 /* 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; 916 916 917 917 if (tei.age) 918 918 ei->age = __calculate_block_age(sbi, cur_age, tei.age); ··· 1114 1114 struct extent_info ei = { 1115 1115 .fofs = fofs, 1116 1116 .len = len, 1117 + .last_blocks = F2FS_EXTENT_AGE_INVALID, 1117 1118 }; 1118 1119 1119 1120 if (!__may_extent_tree(dn->inode, EX_BLOCK_AGE))
+6
fs/f2fs/f2fs.h
··· 707 707 NR_EXTENT_CACHES, 708 708 }; 709 709 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 + 710 716 struct extent_info { 711 717 unsigned int fofs; /* start offset in a file */ 712 718 unsigned int len; /* length of the extent */
+7 -2
fs/f2fs/segment.c
··· 3863 3863 locate_dirty_segment(sbi, GET_SEGNO(sbi, old_blkaddr)); 3864 3864 locate_dirty_segment(sbi, GET_SEGNO(sbi, *new_blkaddr)); 3865 3865 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 + } 3868 3873 3869 3874 up_write(&sit_i->sentry_lock); 3870 3875