Commit a84d5d42 authored by Boris Burkov's avatar Boris Burkov Committed by David Sterba

btrfs: detect nocow for swap after snapshot delete

can_nocow_extent and btrfs_cross_ref_exist both rely on a heuristic for
detecting a must cow condition which is not exactly accurate, but saves
unnecessary tree traversal. The incorrect assumption is that if the
extent was created in a generation smaller than the last snapshot
generation, it must be referenced by that snapshot. That is true, except
the snapshot could have since been deleted, without affecting the last
snapshot generation.

The original patch claimed a performance win from this check, but it
also leads to a bug where you are unable to use a swapfile if you ever
snapshotted the subvolume it's in. Make the check slower and more strict
for the swapon case, without modifying the general cow checks as a
compromise. Turning swap on does not seem to be a particularly
performance sensitive operation, so incurring a possibly unnecessary
btrfs_search_slot seems worthwhile for the added usability.

Note: Until the snapshot is competely cleaned after deletion,
check_committed_refs will still cause the logic to think that cow is
necessary, so the user must until 'btrfs subvolu sync' finished before
activating the swapfile swapon.

CC: stable@vger.kernel.org # 5.4+
Suggested-by: default avatarOmar Sandoval <osandov@osandov.com>
Signed-off-by: default avatarBoris Burkov <boris@bur.io>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent fb2fecba
...@@ -2518,7 +2518,7 @@ int btrfs_pin_extent_for_log_replay(struct btrfs_trans_handle *trans, ...@@ -2518,7 +2518,7 @@ int btrfs_pin_extent_for_log_replay(struct btrfs_trans_handle *trans,
u64 bytenr, u64 num_bytes); u64 bytenr, u64 num_bytes);
int btrfs_exclude_logged_extents(struct extent_buffer *eb); int btrfs_exclude_logged_extents(struct extent_buffer *eb);
int btrfs_cross_ref_exist(struct btrfs_root *root, int btrfs_cross_ref_exist(struct btrfs_root *root,
u64 objectid, u64 offset, u64 bytenr); u64 objectid, u64 offset, u64 bytenr, bool strict);
struct extent_buffer *btrfs_alloc_tree_block(struct btrfs_trans_handle *trans, struct extent_buffer *btrfs_alloc_tree_block(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
u64 parent, u64 root_objectid, u64 parent, u64 root_objectid,
...@@ -2934,7 +2934,7 @@ struct extent_map *btrfs_get_extent_fiemap(struct btrfs_inode *inode, ...@@ -2934,7 +2934,7 @@ struct extent_map *btrfs_get_extent_fiemap(struct btrfs_inode *inode,
u64 start, u64 len); u64 start, u64 len);
noinline int can_nocow_extent(struct inode *inode, u64 offset, u64 *len, noinline int can_nocow_extent(struct inode *inode, u64 offset, u64 *len,
u64 *orig_start, u64 *orig_block_len, u64 *orig_start, u64 *orig_block_len,
u64 *ram_bytes); u64 *ram_bytes, bool strict);
void __btrfs_del_delalloc_inode(struct btrfs_root *root, void __btrfs_del_delalloc_inode(struct btrfs_root *root,
struct btrfs_inode *inode); struct btrfs_inode *inode);
......
...@@ -2306,7 +2306,8 @@ static noinline int check_delayed_ref(struct btrfs_root *root, ...@@ -2306,7 +2306,8 @@ static noinline int check_delayed_ref(struct btrfs_root *root,
static noinline int check_committed_ref(struct btrfs_root *root, static noinline int check_committed_ref(struct btrfs_root *root,
struct btrfs_path *path, struct btrfs_path *path,
u64 objectid, u64 offset, u64 bytenr) u64 objectid, u64 offset, u64 bytenr,
bool strict)
{ {
struct btrfs_fs_info *fs_info = root->fs_info; struct btrfs_fs_info *fs_info = root->fs_info;
struct btrfs_root *extent_root = fs_info->extent_root; struct btrfs_root *extent_root = fs_info->extent_root;
...@@ -2348,9 +2349,13 @@ static noinline int check_committed_ref(struct btrfs_root *root, ...@@ -2348,9 +2349,13 @@ static noinline int check_committed_ref(struct btrfs_root *root,
btrfs_extent_inline_ref_size(BTRFS_EXTENT_DATA_REF_KEY)) btrfs_extent_inline_ref_size(BTRFS_EXTENT_DATA_REF_KEY))
goto out; goto out;
/* If extent created before last snapshot => it's definitely shared */ /*
if (btrfs_extent_generation(leaf, ei) <= * If extent created before last snapshot => it's shared unless the
btrfs_root_last_snapshot(&root->root_item)) * snapshot has been deleted. Use the heuristic if strict is false.
*/
if (!strict &&
(btrfs_extent_generation(leaf, ei) <=
btrfs_root_last_snapshot(&root->root_item)))
goto out; goto out;
iref = (struct btrfs_extent_inline_ref *)(ei + 1); iref = (struct btrfs_extent_inline_ref *)(ei + 1);
...@@ -2375,7 +2380,7 @@ static noinline int check_committed_ref(struct btrfs_root *root, ...@@ -2375,7 +2380,7 @@ static noinline int check_committed_ref(struct btrfs_root *root,
} }
int btrfs_cross_ref_exist(struct btrfs_root *root, u64 objectid, u64 offset, int btrfs_cross_ref_exist(struct btrfs_root *root, u64 objectid, u64 offset,
u64 bytenr) u64 bytenr, bool strict)
{ {
struct btrfs_path *path; struct btrfs_path *path;
int ret; int ret;
...@@ -2386,7 +2391,7 @@ int btrfs_cross_ref_exist(struct btrfs_root *root, u64 objectid, u64 offset, ...@@ -2386,7 +2391,7 @@ int btrfs_cross_ref_exist(struct btrfs_root *root, u64 objectid, u64 offset,
do { do {
ret = check_committed_ref(root, path, objectid, ret = check_committed_ref(root, path, objectid,
offset, bytenr); offset, bytenr, strict);
if (ret && ret != -ENOENT) if (ret && ret != -ENOENT)
goto out; goto out;
......
...@@ -1571,7 +1571,7 @@ static int check_can_nocow(struct btrfs_inode *inode, loff_t pos, ...@@ -1571,7 +1571,7 @@ static int check_can_nocow(struct btrfs_inode *inode, loff_t pos,
} }
ret = can_nocow_extent(&inode->vfs_inode, lockstart, &num_bytes, ret = can_nocow_extent(&inode->vfs_inode, lockstart, &num_bytes,
NULL, NULL, NULL); NULL, NULL, NULL, false);
if (ret <= 0) { if (ret <= 0) {
ret = 0; ret = 0;
if (!nowait) if (!nowait)
......
...@@ -1609,7 +1609,7 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode, ...@@ -1609,7 +1609,7 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode,
goto out_check; goto out_check;
ret = btrfs_cross_ref_exist(root, ino, ret = btrfs_cross_ref_exist(root, ino,
found_key.offset - found_key.offset -
extent_offset, disk_bytenr); extent_offset, disk_bytenr, false);
if (ret) { if (ret) {
/* /*
* ret could be -EIO if the above fails to read * ret could be -EIO if the above fails to read
...@@ -6949,6 +6949,8 @@ static struct extent_map *btrfs_new_extent_direct(struct btrfs_inode *inode, ...@@ -6949,6 +6949,8 @@ static struct extent_map *btrfs_new_extent_direct(struct btrfs_inode *inode,
* @orig_start: (optional) Return the original file offset of the file extent * @orig_start: (optional) Return the original file offset of the file extent
* @orig_len: (optional) Return the original on-disk length of the file extent * @orig_len: (optional) Return the original on-disk length of the file extent
* @ram_bytes: (optional) Return the ram_bytes of the file extent * @ram_bytes: (optional) Return the ram_bytes of the file extent
* @strict: if true, omit optimizations that might force us into unnecessary
* cow. e.g., don't trust generation number.
* *
* This function will flush ordered extents in the range to ensure proper * This function will flush ordered extents in the range to ensure proper
* nocow checks for (nowait == false) case. * nocow checks for (nowait == false) case.
...@@ -6963,7 +6965,7 @@ static struct extent_map *btrfs_new_extent_direct(struct btrfs_inode *inode, ...@@ -6963,7 +6965,7 @@ static struct extent_map *btrfs_new_extent_direct(struct btrfs_inode *inode,
*/ */
noinline int can_nocow_extent(struct inode *inode, u64 offset, u64 *len, noinline int can_nocow_extent(struct inode *inode, u64 offset, u64 *len,
u64 *orig_start, u64 *orig_block_len, u64 *orig_start, u64 *orig_block_len,
u64 *ram_bytes) u64 *ram_bytes, bool strict)
{ {
struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb); struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
struct btrfs_path *path; struct btrfs_path *path;
...@@ -7041,8 +7043,9 @@ noinline int can_nocow_extent(struct inode *inode, u64 offset, u64 *len, ...@@ -7041,8 +7043,9 @@ noinline int can_nocow_extent(struct inode *inode, u64 offset, u64 *len,
* Do the same check as in btrfs_cross_ref_exist but without the * Do the same check as in btrfs_cross_ref_exist but without the
* unnecessary search. * unnecessary search.
*/ */
if (btrfs_file_extent_generation(leaf, fi) <= if (!strict &&
btrfs_root_last_snapshot(&root->root_item)) (btrfs_file_extent_generation(leaf, fi) <=
btrfs_root_last_snapshot(&root->root_item)))
goto out; goto out;
backref_offset = btrfs_file_extent_offset(leaf, fi); backref_offset = btrfs_file_extent_offset(leaf, fi);
...@@ -7078,7 +7081,8 @@ noinline int can_nocow_extent(struct inode *inode, u64 offset, u64 *len, ...@@ -7078,7 +7081,8 @@ noinline int can_nocow_extent(struct inode *inode, u64 offset, u64 *len,
*/ */
ret = btrfs_cross_ref_exist(root, btrfs_ino(BTRFS_I(inode)), ret = btrfs_cross_ref_exist(root, btrfs_ino(BTRFS_I(inode)),
key.offset - backref_offset, disk_bytenr); key.offset - backref_offset, disk_bytenr,
strict);
if (ret) { if (ret) {
ret = 0; ret = 0;
goto out; goto out;
...@@ -7299,7 +7303,7 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map, ...@@ -7299,7 +7303,7 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,
block_start = em->block_start + (start - em->start); block_start = em->block_start + (start - em->start);
if (can_nocow_extent(inode, start, &len, &orig_start, if (can_nocow_extent(inode, start, &len, &orig_start,
&orig_block_len, &ram_bytes) == 1 && &orig_block_len, &ram_bytes, false) == 1 &&
btrfs_inc_nocow_writers(fs_info, block_start)) { btrfs_inc_nocow_writers(fs_info, block_start)) {
struct extent_map *em2; struct extent_map *em2;
...@@ -10130,7 +10134,7 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, ...@@ -10130,7 +10134,7 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file,
free_extent_map(em); free_extent_map(em);
em = NULL; em = NULL;
ret = can_nocow_extent(inode, start, &len, NULL, NULL, NULL); ret = can_nocow_extent(inode, start, &len, NULL, NULL, NULL, true);
if (ret < 0) { if (ret < 0) {
goto out; goto out;
} else if (ret) { } else if (ret) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment