Skip to content

Commit

Permalink
Btrfs: Fix extent replacment race
Browse files Browse the repository at this point in the history
Data COW means that whenever we write to a file, we replace any old
extent pointers with new ones.  There was a window where a readpage
might find the old extent pointers on disk and cache them in the
extent_map tree in ram in the middle of a given write replacing them.

Even though both the readpage and the write had their respective bytes
in the file locked, the extent readpage inserts may cover more bytes than
it had locked down.

This commit closes the race by keeping the new extent pinned in the extent
map tree until after the on-disk btree is properly setup with the new
extent pointers.

Signed-off-by: Chris Mason <[email protected]>
  • Loading branch information
chrismason-xx committed Sep 11, 2009
1 parent 8b62b72 commit a1ed835
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 13 deletions.
2 changes: 1 addition & 1 deletion fs/btrfs/ctree.h
Original file line number Diff line number Diff line change
Expand Up @@ -2292,7 +2292,7 @@ extern struct file_operations btrfs_file_operations;
int btrfs_drop_extents(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct inode *inode,
u64 start, u64 end, u64 locked_end,
u64 inline_limit, u64 *hint_block);
u64 inline_limit, u64 *hint_block, int drop_cache);
int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct inode *inode, u64 start, u64 end);
Expand Down
50 changes: 50 additions & 0 deletions fs/btrfs/extent_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,56 @@ static int mergable_maps(struct extent_map *prev, struct extent_map *next)
return 0;
}

int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len)
{
int ret = 0;
struct extent_map *merge = NULL;
struct rb_node *rb;
struct extent_map *em;

write_lock(&tree->lock);
em = lookup_extent_mapping(tree, start, len);

WARN_ON(em->start != start || !em);

if (!em)
goto out;

clear_bit(EXTENT_FLAG_PINNED, &em->flags);

if (em->start != 0) {
rb = rb_prev(&em->rb_node);
if (rb)
merge = rb_entry(rb, struct extent_map, rb_node);
if (rb && mergable_maps(merge, em)) {
em->start = merge->start;
em->len += merge->len;
em->block_len += merge->block_len;
em->block_start = merge->block_start;
merge->in_tree = 0;
rb_erase(&merge->rb_node, &tree->map);
free_extent_map(merge);
}
}

rb = rb_next(&em->rb_node);
if (rb)
merge = rb_entry(rb, struct extent_map, rb_node);
if (rb && mergable_maps(em, merge)) {
em->len += merge->len;
em->block_len += merge->len;
rb_erase(&merge->rb_node, &tree->map);
merge->in_tree = 0;
free_extent_map(merge);
}

free_extent_map(em);
out:
write_unlock(&tree->lock);
return ret;

}

/**
* add_extent_mapping - add new extent map to the extent tree
* @tree: tree to insert new map in
Expand Down
1 change: 1 addition & 0 deletions fs/btrfs/extent_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,5 @@ struct extent_map *alloc_extent_map(gfp_t mask);
void free_extent_map(struct extent_map *em);
int __init extent_map_init(void);
void extent_map_exit(void);
int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len);
#endif
8 changes: 5 additions & 3 deletions fs/btrfs/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
}
flags = em->flags;
if (skip_pinned && test_bit(EXTENT_FLAG_PINNED, &em->flags)) {
write_unlock(&em_tree->lock);
if (em->start <= start &&
(!testend || em->start + em->len >= start + len)) {
free_extent_map(em);
write_unlock(&em_tree->lock);
break;
}
if (start < em->start) {
Expand All @@ -190,6 +190,7 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
start = em->start + em->len;
}
free_extent_map(em);
write_unlock(&em_tree->lock);
continue;
}
compressed = test_bit(EXTENT_FLAG_COMPRESSED, &em->flags);
Expand Down Expand Up @@ -269,7 +270,7 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
noinline int btrfs_drop_extents(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct inode *inode,
u64 start, u64 end, u64 locked_end,
u64 inline_limit, u64 *hint_byte)
u64 inline_limit, u64 *hint_byte, int drop_cache)
{
u64 extent_end = 0;
u64 search_start = start;
Expand All @@ -294,7 +295,8 @@ noinline int btrfs_drop_extents(struct btrfs_trans_handle *trans,
int ret;

inline_limit = 0;
btrfs_drop_extent_cache(inode, start, end - 1, 0);
if (drop_cache)
btrfs_drop_extent_cache(inode, start, end - 1, 0);

path = btrfs_alloc_path();
if (!path)
Expand Down
25 changes: 20 additions & 5 deletions fs/btrfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ static noinline int cow_file_range_inline(struct btrfs_trans_handle *trans,
}

ret = btrfs_drop_extents(trans, root, inode, start,
aligned_end, aligned_end, start, &hint_byte);
aligned_end, aligned_end, start,
&hint_byte, 1);
BUG_ON(ret);

if (isize > actual_end)
Expand All @@ -241,7 +242,7 @@ static noinline int cow_file_range_inline(struct btrfs_trans_handle *trans,
inline_len, compressed_size,
compressed_pages);
BUG_ON(ret);
btrfs_drop_extent_cache(inode, start, aligned_end, 0);
btrfs_drop_extent_cache(inode, start, aligned_end - 1, 0);
return 0;
}

Expand Down Expand Up @@ -1455,9 +1456,19 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
BUG_ON(!path);

path->leave_spinning = 1;

/*
* we may be replacing one extent in the tree with another.
* The new extent is pinned in the extent map, and we don't want
* to drop it from the cache until it is completely in the btree.
*
* So, tell btrfs_drop_extents to leave this extent in the cache.
* the caller is expected to unpin it and allow it to be merged
* with the others.
*/
ret = btrfs_drop_extents(trans, root, inode, file_pos,
file_pos + num_bytes, locked_end,
file_pos, &hint);
file_pos, &hint, 0);
BUG_ON(ret);

ins.objectid = inode->i_ino;
Expand Down Expand Up @@ -1485,7 +1496,6 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
btrfs_mark_buffer_dirty(leaf);

inode_add_bytes(inode, num_bytes);
btrfs_drop_extent_cache(inode, file_pos, file_pos + num_bytes - 1, 0);

ins.objectid = disk_bytenr;
ins.offset = disk_num_bytes;
Expand Down Expand Up @@ -1596,6 +1606,9 @@ static int btrfs_finish_ordered_io(struct inode *inode, u64 start, u64 end)
ordered_extent->len,
compressed, 0, 0,
BTRFS_FILE_EXTENT_REG);
unpin_extent_cache(&BTRFS_I(inode)->extent_tree,
ordered_extent->file_offset,
ordered_extent->len);
BUG_ON(ret);
}
unlock_extent(io_tree, ordered_extent->file_offset,
Expand Down Expand Up @@ -2940,7 +2953,7 @@ int btrfs_cont_expand(struct inode *inode, loff_t size)
cur_offset,
cur_offset + hole_size,
block_end,
cur_offset, &hint_byte);
cur_offset, &hint_byte, 1);
if (err)
break;
err = btrfs_insert_file_extent(trans, root,
Expand Down Expand Up @@ -5086,6 +5099,8 @@ static int prealloc_file_range(struct btrfs_trans_handle *trans,
0, 0, 0,
BTRFS_FILE_EXTENT_PREALLOC);
BUG_ON(ret);
btrfs_drop_extent_cache(inode, cur_offset,
cur_offset + ins.offset -1, 0);
num_bytes -= ins.offset;
cur_offset += ins.offset;
alloc_hint = ins.objectid + ins.offset;
Expand Down
5 changes: 2 additions & 3 deletions fs/btrfs/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -597,9 +597,8 @@ static int btrfs_defrag_file(struct file *file)
clear_page_dirty_for_io(page);

btrfs_set_extent_delalloc(inode, page_start, page_end);

unlock_extent(io_tree, page_start, page_end, GFP_NOFS);
set_page_dirty(page);
unlock_extent(io_tree, page_start, page_end, GFP_NOFS);
unlock_page(page);
page_cache_release(page);
balance_dirty_pages_ratelimited_nr(inode->i_mapping, 1);
Expand Down Expand Up @@ -977,7 +976,7 @@ static long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,

/* punch hole in destination first */
btrfs_drop_extents(trans, root, inode, off, off + len,
off + len, 0, &hint_byte);
off + len, 0, &hint_byte, 1);

/* clone data */
key.objectid = src->i_ino;
Expand Down
2 changes: 1 addition & 1 deletion fs/btrfs/tree-log.c
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ static noinline int replay_one_extent(struct btrfs_trans_handle *trans,
saved_nbytes = inode_get_bytes(inode);
/* drop any overlapping extents */
ret = btrfs_drop_extents(trans, root, inode,
start, extent_end, extent_end, start, &alloc_hint);
start, extent_end, extent_end, start, &alloc_hint, 1);
BUG_ON(ret);

if (found_type == BTRFS_FILE_EXTENT_REG ||
Expand Down

0 comments on commit a1ed835

Please sign in to comment.