btrfs: qgroup: Fix qgroup reserved space underflow by only freeing reserved ranges
[BUG] For the following case, btrfs can underflow qgroup reserved space at an error path: (Page size 4K, function name without "btrfs_" prefix) Task A | Task B ---------------------------------------------------------------------- Buffered_write [0, 2K) | |- check_data_free_space() | | |- qgroup_reserve_data() | | Range aligned to page | | range [0, 4K) <<< | | 4K bytes reserved <<< | |- copy pages to page cache | | Buffered_write [2K, 4K) | |- check_data_free_space() | | |- qgroup_reserved_data() | | Range alinged to page | | range [0, 4K) | | Already reserved by A <<< | | 0 bytes reserved <<< | |- delalloc_reserve_metadata() | | And it *FAILED* (Maybe EQUOTA) | |- free_reserved_data_space() |- qgroup_free_data() Range aligned to page range [0, 4K) Freeing 4K (Special thanks to Chandan for the detailed report and analyse) [CAUSE] Above Task B is freeing reserved data range [0, 4K) which is actually reserved by Task A. And at writeback time, page dirty by Task A will go through writeback routine, which will free 4K reserved data space at file extent insert time, causing the qgroup underflow. [FIX] For btrfs_qgroup_free_data(), add @reserved parameter to only free data ranges reserved by previous btrfs_qgroup_reserve_data(). So in above case, Task B will try to free 0 byte, so no underflow. Reported-by: Chandan Rajendra <chandan@linux.vnet.ibm.com> Signed-off-by: Qu Wenruo <quwenruo@cn.fujitsu.com> Reviewed-by: Chandan Rajendra <chandan@linux.vnet.ibm.com> Tested-by: Chandan Rajendra <chandan@linux.vnet.ibm.com> Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
@@ -1660,8 +1660,9 @@ static noinline ssize_t __btrfs_buffered_write(struct file *file,
|
||||
reserve_bytes);
|
||||
if (ret) {
|
||||
if (!only_release_metadata)
|
||||
btrfs_free_reserved_data_space(inode, pos,
|
||||
write_bytes);
|
||||
btrfs_free_reserved_data_space(inode,
|
||||
data_reserved, pos,
|
||||
write_bytes);
|
||||
else
|
||||
btrfs_end_write_no_snapshoting(root);
|
||||
break;
|
||||
@@ -1743,8 +1744,9 @@ again:
|
||||
__pos = round_down(pos,
|
||||
fs_info->sectorsize) +
|
||||
(dirty_pages << PAGE_SHIFT);
|
||||
btrfs_delalloc_release_space(inode, __pos,
|
||||
release_bytes);
|
||||
btrfs_delalloc_release_space(inode,
|
||||
data_reserved, __pos,
|
||||
release_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1799,9 +1801,9 @@ again:
|
||||
btrfs_delalloc_release_metadata(BTRFS_I(inode),
|
||||
release_bytes);
|
||||
} else {
|
||||
btrfs_delalloc_release_space(inode,
|
||||
round_down(pos, fs_info->sectorsize),
|
||||
release_bytes);
|
||||
btrfs_delalloc_release_space(inode, data_reserved,
|
||||
round_down(pos, fs_info->sectorsize),
|
||||
release_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2918,8 +2920,8 @@ static long btrfs_fallocate(struct file *file, int mode,
|
||||
* range, free reserved data space first, otherwise
|
||||
* it'll result in false ENOSPC error.
|
||||
*/
|
||||
btrfs_free_reserved_data_space(inode, cur_offset,
|
||||
last_byte - cur_offset);
|
||||
btrfs_free_reserved_data_space(inode, data_reserved,
|
||||
cur_offset, last_byte - cur_offset);
|
||||
}
|
||||
free_extent_map(em);
|
||||
cur_offset = last_byte;
|
||||
@@ -2938,8 +2940,9 @@ static long btrfs_fallocate(struct file *file, int mode,
|
||||
range->len, i_blocksize(inode),
|
||||
offset + len, &alloc_hint);
|
||||
else
|
||||
btrfs_free_reserved_data_space(inode, range->start,
|
||||
range->len);
|
||||
btrfs_free_reserved_data_space(inode,
|
||||
data_reserved, range->start,
|
||||
range->len);
|
||||
list_del(&range->list);
|
||||
kfree(range);
|
||||
}
|
||||
@@ -2977,8 +2980,8 @@ out:
|
||||
inode_unlock(inode);
|
||||
/* Let go of our reservation. */
|
||||
if (ret != 0)
|
||||
btrfs_free_reserved_data_space(inode, alloc_start,
|
||||
alloc_end - cur_offset);
|
||||
btrfs_free_reserved_data_space(inode, data_reserved,
|
||||
alloc_start, alloc_end - cur_offset);
|
||||
extent_changeset_free(data_reserved);
|
||||
return ret;
|
||||
}
|
||||
|
Reference in New Issue
Block a user