btrfs: move btrfs_truncate_block out of trans handle
authorJosef Bacik <josef@toxicpanda.com>
Thu, 19 Oct 2017 18:16:02 +0000 (14:16 -0400)
committerDavid Sterba <dsterba@suse.com>
Wed, 1 Nov 2017 19:45:35 +0000 (20:45 +0100)
Since we do a delalloc reserve in btrfs_truncate_block we can deadlock
with freeze.  If somebody else is trying to allocate metadata for this
inode and it gets stuck in start_delalloc_inodes because of freeze we
will deadlock.  Be safe and move this outside of a trans handle.  This
also has a side-effect of making sure that we're not leaving stale data
behind in the other_encoding or encryption case.  Not an issue now since
nobody uses it, but it would be a problem in the future.

Signed-off-by: Josef Bacik <jbacik@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/inode.c

index b71731ef28c474af7ad2acc4f7b8bd07495005c8..b93fe05a39c7643247298c4b7d12eccae39b9790 100644 (file)
@@ -4360,47 +4360,11 @@ static int truncate_space_check(struct btrfs_trans_handle *trans,
 
 }
 
-static int truncate_inline_extent(struct inode *inode,
-                                 struct btrfs_path *path,
-                                 struct btrfs_key *found_key,
-                                 const u64 item_end,
-                                 const u64 new_size)
-{
-       struct extent_buffer *leaf = path->nodes[0];
-       int slot = path->slots[0];
-       struct btrfs_file_extent_item *fi;
-       u32 size = (u32)(new_size - found_key->offset);
-       struct btrfs_root *root = BTRFS_I(inode)->root;
-
-       fi = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item);
-
-       if (btrfs_file_extent_compression(leaf, fi) != BTRFS_COMPRESS_NONE) {
-               loff_t offset = new_size;
-               loff_t page_end = ALIGN(offset, PAGE_SIZE);
-
-               /*
-                * Zero out the remaining of the last page of our inline extent,
-                * instead of directly truncating our inline extent here - that
-                * would be much more complex (decompressing all the data, then
-                * compressing the truncated data, which might be bigger than
-                * the size of the inline extent, resize the extent, etc).
-                * We release the path because to get the page we might need to
-                * read the extent item from disk (data not in the page cache).
-                */
-               btrfs_release_path(path);
-               return btrfs_truncate_block(inode, offset, page_end - offset,
-                                       0);
-       }
-
-       btrfs_set_file_extent_ram_bytes(leaf, fi, size);
-       size = btrfs_file_extent_calc_inline_size(size);
-       btrfs_truncate_item(root->fs_info, path, size, 1);
-
-       if (test_bit(BTRFS_ROOT_REF_COWS, &root->state))
-               inode_sub_bytes(inode, item_end + 1 - new_size);
-
-       return 0;
-}
+/*
+ * Return this if we need to call truncate_block for the last bit of the
+ * truncate.
+ */
+#define NEED_TRUNCATE_BLOCK 1
 
 /*
  * this can truncate away extent items, csum items and directory items.
@@ -4561,11 +4525,6 @@ search_again:
                if (found_type != BTRFS_EXTENT_DATA_KEY)
                        goto delete;
 
-               if (del_item)
-                       last_size = found_key.offset;
-               else
-                       last_size = new_size;
-
                if (extent_type != BTRFS_FILE_EXTENT_INLINE) {
                        u64 num_dec;
                        extent_start = btrfs_file_extent_disk_bytenr(leaf, fi);
@@ -4607,40 +4566,30 @@ search_again:
                         */
                        if (!del_item &&
                            btrfs_file_extent_encryption(leaf, fi) == 0 &&
-                           btrfs_file_extent_other_encoding(leaf, fi) == 0) {
-
+                           btrfs_file_extent_other_encoding(leaf, fi) == 0 &&
+                           btrfs_file_extent_compression(leaf, fi) == 0) {
+                               u32 size = (u32)(new_size - found_key.offset);
+
+                               btrfs_set_file_extent_ram_bytes(leaf, fi, size);
+                               size = btrfs_file_extent_calc_inline_size(size);
+                               btrfs_truncate_item(root->fs_info, path, size, 1);
+                       } else if (!del_item) {
                                /*
-                                * Need to release path in order to truncate a
-                                * compressed extent. So delete any accumulated
-                                * extent items so far.
+                                * We have to bail so the last_size is set to
+                                * just before this extent.
                                 */
-                               if (btrfs_file_extent_compression(leaf, fi) !=
-                                   BTRFS_COMPRESS_NONE && pending_del_nr) {
-                                       err = btrfs_del_items(trans, root, path,
-                                                             pending_del_slot,
-                                                             pending_del_nr);
-                                       if (err) {
-                                               btrfs_abort_transaction(trans,
-                                                                       err);
-                                               goto error;
-                                       }
-                                       pending_del_nr = 0;
-                               }
+                               err = NEED_TRUNCATE_BLOCK;
+                               break;
+                       }
 
-                               err = truncate_inline_extent(inode, path,
-                                                            &found_key,
-                                                            item_end,
-                                                            new_size);
-                               if (err) {
-                                       btrfs_abort_transaction(trans, err);
-                                       goto error;
-                               }
-                       } else if (test_bit(BTRFS_ROOT_REF_COWS,
-                                           &root->state)) {
+                       if (test_bit(BTRFS_ROOT_REF_COWS, &root->state))
                                inode_sub_bytes(inode, item_end + 1 - new_size);
-                       }
                }
 delete:
+               if (del_item)
+                       last_size = found_key.offset;
+               else
+                       last_size = new_size;
                if (del_item) {
                        if (!pending_del_nr) {
                                /* no pending yet, add ourselves */
@@ -9338,12 +9287,12 @@ static int btrfs_truncate(struct inode *inode)
                ret = btrfs_truncate_inode_items(trans, root, inode,
                                                 inode->i_size,
                                                 BTRFS_EXTENT_DATA_KEY);
+               trans->block_rsv = &fs_info->trans_block_rsv;
                if (ret != -ENOSPC && ret != -EAGAIN) {
                        err = ret;
                        break;
                }
 
-               trans->block_rsv = &fs_info->trans_block_rsv;
                ret = btrfs_update_inode(trans, root, inode);
                if (ret) {
                        err = ret;
@@ -9367,6 +9316,27 @@ static int btrfs_truncate(struct inode *inode)
                trans->block_rsv = rsv;
        }
 
+       /*
+        * We can't call btrfs_truncate_block inside a trans handle as we could
+        * deadlock with freeze, if we got NEED_TRUNCATE_BLOCK then we know
+        * we've truncated everything except the last little bit, and can do
+        * btrfs_truncate_block and then update the disk_i_size.
+        */
+       if (ret == NEED_TRUNCATE_BLOCK) {
+               btrfs_end_transaction(trans);
+               btrfs_btree_balance_dirty(fs_info);
+
+               ret = btrfs_truncate_block(inode, inode->i_size, 0, 0);
+               if (ret)
+                       goto out;
+               trans = btrfs_start_transaction(root, 1);
+               if (IS_ERR(trans)) {
+                       ret = PTR_ERR(trans);
+                       goto out;
+               }
+               btrfs_ordered_update_i_size(inode, inode->i_size, NULL);
+       }
+
        if (ret == 0 && inode->i_nlink > 0) {
                trans->block_rsv = root->orphan_block_rsv;
                ret = btrfs_orphan_del(trans, BTRFS_I(inode));