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:
Josef Bacik
2017-07-24 15:14:25 -04:00
committed by David Sterba
parent 8d8aafeea2
commit 23b5ec7494
4 changed files with 110 additions and 34 deletions

View File

@@ -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);