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

ext4: ensure f_bfree returned by ext4_statfs() is non-negative

I found the issue that the number of free blocks went negative.
# stat -f /mnt/mp1/
File: "/mnt/mp1/"
ID: e175ccb83a872efe Namelen: 255 Type: ext2/ext3
Block size: 4096 Fundamental block size: 4096
Blocks: Total: 258022 Free: -15 Available: -13122
Inodes: Total: 65536 Free: 63029

f_bfree in struct statfs will go negative when the filesystem has
few free blocks. Because the number of dirty blocks is bigger than
the number of free blocks in the following two cases.

CASE 1:
ext4_da_writepages
mpage_da_map_and_submit
ext4_map_blocks
ext4_ext_map_blocks
ext4_mb_new_blocks
ext4_mb_diskspace_used
percpu_counter_sub(&sbi->s_freeblocks_counter, ac->ac_b_ex.fe_len);
<--- interrupt statfs systemcall --->
ext4_da_update_reserve_space
percpu_counter_sub(&sbi->s_dirtyblocks_counter,
used + ei->i_allocated_meta_blocks);

CASE 2:
ext4_write_begin
__block_write_begin
ext4_map_blocks
ext4_ext_map_blocks
ext4_mb_new_blocks
ext4_mb_diskspace_used
percpu_counter_sub(&sbi->s_freeblocks_counter, ac->ac_b_ex.fe_len);
<--- interrupt statfs systemcall --->
percpu_counter_sub(&sbi->s_dirtyblocks_counter, reserv_blks);

To avoid the issue, this patch ensures that f_bfree is non-negative.

Signed-off-by: Kazuya Mio <k-mio@sx.jp.nec.com>

authored by

Kazuya Mio and committed by
Theodore Ts'o
d02a9391 28739eea

+4 -1
+4 -1
fs/ext4/super.c
··· 4458 4458 struct ext4_sb_info *sbi = EXT4_SB(sb); 4459 4459 struct ext4_super_block *es = sbi->s_es; 4460 4460 u64 fsid; 4461 + s64 bfree; 4461 4462 4462 4463 if (test_opt(sb, MINIX_DF)) { 4463 4464 sbi->s_overhead_last = 0; ··· 4502 4501 buf->f_type = EXT4_SUPER_MAGIC; 4503 4502 buf->f_bsize = sb->s_blocksize; 4504 4503 buf->f_blocks = ext4_blocks_count(es) - sbi->s_overhead_last; 4505 - buf->f_bfree = percpu_counter_sum_positive(&sbi->s_freeblocks_counter) - 4504 + bfree = percpu_counter_sum_positive(&sbi->s_freeblocks_counter) - 4506 4505 percpu_counter_sum_positive(&sbi->s_dirtyblocks_counter); 4506 + /* prevent underflow in case that few free space is available */ 4507 + buf->f_bfree = max_t(s64, bfree, 0); 4507 4508 buf->f_bavail = buf->f_bfree - ext4_r_blocks_count(es); 4508 4509 if (buf->f_bfree < ext4_r_blocks_count(es)) 4509 4510 buf->f_bavail = 0;