Skip to content

Commit

Permalink
Btrfs: fix fallocate deadlock on inode extent lock
Browse files Browse the repository at this point in the history
The btrfs fallocate call takes an extent lock on the entire range
being fallocated, and then runs through insert_reserved_extent on each
extent as they are allocated.

The problem with this is that btrfs_drop_extents may decide to try
and take the same extent lock fallocate was already holding.  The solution
used here is to push down knowledge of the range that is already locked
going into btrfs_drop_extents.

It turns out that at least one other caller had the same bug.

Signed-off-by: Chris Mason <chris.mason@oracle.com>
  • Loading branch information
Chris Mason committed Apr 24, 2009
1 parent 9601e3f commit e980b50
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 17 deletions.
3 changes: 2 additions & 1 deletion fs/btrfs/ctree.h
Original file line number Diff line number Diff line change
Expand Up @@ -2177,7 +2177,8 @@ int btrfs_check_file(struct btrfs_root *root, struct inode *inode);
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 inline_limit, u64 *hint_block);
u64 start, u64 end, u64 locked_end,
u64 inline_limit, u64 *hint_block);
int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct inode *inode, u64 start, u64 end);
Expand Down
11 changes: 6 additions & 5 deletions fs/btrfs/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -363,15 +363,16 @@ int btrfs_check_file(struct btrfs_root *root, struct inode *inode)
*/
noinline int btrfs_drop_extents(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct inode *inode,
u64 start, u64 end, u64 inline_limit, u64 *hint_byte)
u64 start, u64 end, u64 locked_end,
u64 inline_limit, u64 *hint_byte)
{
u64 extent_end = 0;
u64 locked_end = end;
u64 search_start = start;
u64 leaf_start;
u64 ram_bytes = 0;
u64 orig_parent = 0;
u64 disk_bytenr = 0;
u64 orig_locked_end = locked_end;
u8 compression;
u8 encryption;
u16 other_encoding = 0;
Expand Down Expand Up @@ -684,9 +685,9 @@ noinline int btrfs_drop_extents(struct btrfs_trans_handle *trans,
}
out:
btrfs_free_path(path);
if (locked_end > end) {
unlock_extent(&BTRFS_I(inode)->io_tree, end, locked_end - 1,
GFP_NOFS);
if (locked_end > orig_locked_end) {
unlock_extent(&BTRFS_I(inode)->io_tree, orig_locked_end,
locked_end - 1, GFP_NOFS);
}
btrfs_check_file(root, inode);
return ret;
Expand Down
27 changes: 18 additions & 9 deletions fs/btrfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ static noinline int cow_file_range_inline(struct btrfs_trans_handle *trans,
}

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

if (isize > actual_end)
Expand Down Expand Up @@ -1439,6 +1439,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
struct inode *inode, u64 file_pos,
u64 disk_bytenr, u64 disk_num_bytes,
u64 num_bytes, u64 ram_bytes,
u64 locked_end,
u8 compression, u8 encryption,
u16 other_encoding, int extent_type)
{
Expand All @@ -1455,7 +1456,8 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,

path->leave_spinning = 1;
ret = btrfs_drop_extents(trans, root, inode, file_pos,
file_pos + num_bytes, file_pos, &hint);
file_pos + num_bytes, locked_end,
file_pos, &hint);
BUG_ON(ret);

ins.objectid = inode->i_ino;
Expand Down Expand Up @@ -1590,6 +1592,8 @@ static int btrfs_finish_ordered_io(struct inode *inode, u64 start, u64 end)
ordered_extent->disk_len,
ordered_extent->len,
ordered_extent->len,
ordered_extent->file_offset +
ordered_extent->len,
compressed, 0, 0,
BTRFS_FILE_EXTENT_REG);
BUG_ON(ret);
Expand Down Expand Up @@ -2877,6 +2881,7 @@ int btrfs_cont_expand(struct inode *inode, loff_t size)
err = btrfs_drop_extents(trans, root, inode,
cur_offset,
cur_offset + hole_size,
block_end,
cur_offset, &hint_byte);
if (err)
break;
Expand Down Expand Up @@ -4968,7 +4973,7 @@ static int btrfs_symlink(struct inode *dir, struct dentry *dentry,

static int prealloc_file_range(struct btrfs_trans_handle *trans,
struct inode *inode, u64 start, u64 end,
u64 alloc_hint, int mode)
u64 locked_end, u64 alloc_hint, int mode)
{
struct btrfs_root *root = BTRFS_I(inode)->root;
struct btrfs_key ins;
Expand All @@ -4989,7 +4994,8 @@ static int prealloc_file_range(struct btrfs_trans_handle *trans,
ret = insert_reserved_file_extent(trans, inode,
cur_offset, ins.objectid,
ins.offset, ins.offset,
ins.offset, 0, 0, 0,
ins.offset, locked_end,
0, 0, 0,
BTRFS_FILE_EXTENT_PREALLOC);
BUG_ON(ret);
num_bytes -= ins.offset;
Expand Down Expand Up @@ -5018,6 +5024,7 @@ static long btrfs_fallocate(struct inode *inode, int mode,
u64 alloc_start;
u64 alloc_end;
u64 alloc_hint = 0;
u64 locked_end;
u64 mask = BTRFS_I(inode)->root->sectorsize - 1;
struct extent_map *em;
struct btrfs_trans_handle *trans;
Expand All @@ -5039,6 +5046,7 @@ static long btrfs_fallocate(struct inode *inode, int mode,
goto out;
}

locked_end = alloc_end - 1;
while (1) {
struct btrfs_ordered_extent *ordered;

Expand All @@ -5051,16 +5059,16 @@ static long btrfs_fallocate(struct inode *inode, int mode,
/* the extent lock is ordered inside the running
* transaction
*/
lock_extent(&BTRFS_I(inode)->io_tree, alloc_start,
alloc_end - 1, GFP_NOFS);
lock_extent(&BTRFS_I(inode)->io_tree, alloc_start, locked_end,
GFP_NOFS);
ordered = btrfs_lookup_first_ordered_extent(inode,
alloc_end - 1);
if (ordered &&
ordered->file_offset + ordered->len > alloc_start &&
ordered->file_offset < alloc_end) {
btrfs_put_ordered_extent(ordered);
unlock_extent(&BTRFS_I(inode)->io_tree,
alloc_start, alloc_end - 1, GFP_NOFS);
alloc_start, locked_end, GFP_NOFS);
btrfs_end_transaction(trans, BTRFS_I(inode)->root);

/*
Expand All @@ -5085,7 +5093,8 @@ static long btrfs_fallocate(struct inode *inode, int mode,
last_byte = (last_byte + mask) & ~mask;
if (em->block_start == EXTENT_MAP_HOLE) {
ret = prealloc_file_range(trans, inode, cur_offset,
last_byte, alloc_hint, mode);
last_byte, locked_end + 1,
alloc_hint, mode);
if (ret < 0) {
free_extent_map(em);
break;
Expand All @@ -5101,7 +5110,7 @@ static long btrfs_fallocate(struct inode *inode, int mode,
break;
}
}
unlock_extent(&BTRFS_I(inode)->io_tree, alloc_start, alloc_end - 1,
unlock_extent(&BTRFS_I(inode)->io_tree, alloc_start, locked_end,
GFP_NOFS);

btrfs_end_transaction(trans, BTRFS_I(inode)->root);
Expand Down
3 changes: 2 additions & 1 deletion fs/btrfs/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,8 @@ static long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
BUG_ON(!trans);

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

/* 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 @@ -536,7 +536,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, start, &alloc_hint);
start, extent_end, extent_end, start, &alloc_hint);
BUG_ON(ret);

if (found_type == BTRFS_FILE_EXTENT_REG ||
Expand Down

0 comments on commit e980b50

Please sign in to comment.