• Wang Xiaoguang's avatar
    btrfs: fix disk_i_size update bug when fallocate() fails · c0d2f610
    Wang Xiaoguang authored
    When doing truncate operation, btrfs_setsize() will first call
    truncate_setsize() to set new inode->i_size, but if later
    btrfs_truncate() fails, btrfs_setsize() will call
    "i_size_write(inode, BTRFS_I(inode)->disk_i_size)" to reset the
    inmemory inode size, now bug occurs. It's because for truncate
    case btrfs_ordered_update_i_size() directly uses inode->i_size
    to update BTRFS_I(inode)->disk_i_size, indeed we should use the
    "offset" argument to update disk_i_size. Here is the call graph:
    ==>btrfs_truncate()
    ====>btrfs_truncate_inode_items()
    ======>btrfs_ordered_update_i_size(inode, last_size, NULL);
    Here btrfs_ordered_update_i_size()'s offset argument is last_size.
    
    And below test case can reveal this bug:
    
    dd if=/dev/zero of=fs.img bs=$((1024*1024)) count=100
    dev=$(losetup --show -f fs.img)
    mkdir -p /mnt/mntpoint
    mkfs.btrfs  -f $dev
    mount $dev /mnt/mntpoint
    cd /mnt/mntpoint
    
    echo "workdir is: /mnt/mntpoint"
    blocksize=$((128 * 1024))
    dd if=/dev/zero of=testfile bs=$blocksize count=1
    sync
    count=$((17*1024*1024*1024/blocksize))
    echo "file size is:" $((count*blocksize))
    for ((i = 1; i <= $count; i++)); do
    	i=$((i + 1))
    	dst_offset=$((blocksize * i))
    	xfs_io -f -c "reflink testfile 0 $dst_offset $blocksize"\
    		testfile > /dev/null
    done
    sync
    
    truncate --size 0 testfile
    ls -l testfile
    du -sh testfile
    exit
    
    In this case, truncate operation will fail for enospc reason and
    "du -sh testfile" returns value greater than 0, but testfile's
    size is 0, we need to reflect correct inode->i_size.
    Signed-off-by: default avatarWang Xiaoguang <wangxg.fnst@cn.fujitsu.com>
    Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
    Signed-off-by: default avatarChris Mason <clm@fb.com>
    c0d2f610
ordered-data.c 31 KB