btrfs: fix readdir deadlock with pagefault
Readdir does dir_emit while under the btree lock. dir_emit can trigger
the page fault which means we can deadlock. Fix this by allocating a
buffer on opening a directory and copying the readdir into this buffer
and doing dir_emit from outside of the tree lock.
Thread A
readdir <holding tree lock>
dir_emit
<page fault>
down_read(mmap_sem)
Thread B
mmap write
down_write(mmap_sem)
page_mkwrite
wait_ordered_extents
Process C
finish_ordered_extent
insert_reserved_file_extent
try to lock leaf <hang>
Signed-off-by: Josef Bacik <jbacik@fb.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ copy the deadlock scenario to changelog ]
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
committed by
David Sterba
parent
8d8aafeea2
commit
23b5ec7494
@@ -3966,6 +3966,7 @@ static long btrfs_ioctl_trans_start(struct file *file)
|
||||
struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
|
||||
struct btrfs_root *root = BTRFS_I(inode)->root;
|
||||
struct btrfs_trans_handle *trans;
|
||||
struct btrfs_file_private *private;
|
||||
int ret;
|
||||
static bool warned = false;
|
||||
|
||||
@@ -3984,8 +3985,16 @@ static long btrfs_ioctl_trans_start(struct file *file)
|
||||
}
|
||||
|
||||
ret = -EINPROGRESS;
|
||||
if (file->private_data)
|
||||
private = file->private_data;
|
||||
if (private && private->trans)
|
||||
goto out;
|
||||
if (!private) {
|
||||
private = kzalloc(sizeof(struct btrfs_file_private),
|
||||
GFP_KERNEL);
|
||||
if (!private)
|
||||
return -ENOMEM;
|
||||
file->private_data = private;
|
||||
}
|
||||
|
||||
ret = -EROFS;
|
||||
if (btrfs_root_readonly(root))
|
||||
@@ -4002,7 +4011,7 @@ static long btrfs_ioctl_trans_start(struct file *file)
|
||||
if (IS_ERR(trans))
|
||||
goto out_drop;
|
||||
|
||||
file->private_data = trans;
|
||||
private->trans = trans;
|
||||
return 0;
|
||||
|
||||
out_drop:
|
||||
@@ -4257,14 +4266,13 @@ long btrfs_ioctl_trans_end(struct file *file)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct btrfs_root *root = BTRFS_I(inode)->root;
|
||||
struct btrfs_trans_handle *trans;
|
||||
struct btrfs_file_private *private = file->private_data;
|
||||
|
||||
trans = file->private_data;
|
||||
if (!trans)
|
||||
if (!private || !private->trans)
|
||||
return -EINVAL;
|
||||
file->private_data = NULL;
|
||||
|
||||
btrfs_end_transaction(trans);
|
||||
btrfs_end_transaction(private->trans);
|
||||
private->trans = NULL;
|
||||
|
||||
atomic_dec(&root->fs_info->open_ioctl_trans);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user