Btrfs: fix clone to deal with holes when NO_HOLES feature is enabled
[firefly-linux-kernel-4.4.55.git] / fs / btrfs / ioctl.c
index 55f4d717d541917e3efc03a7f95a6ed437b482bf..44dcfd054ca6bb42207358675c7804056afe0325 100644 (file)
@@ -3012,6 +3012,37 @@ out:
        return ret;
 }
 
+static int clone_finish_inode_update(struct btrfs_trans_handle *trans,
+                                    struct inode *inode,
+                                    u64 endoff,
+                                    const u64 destoff,
+                                    const u64 olen)
+{
+       struct btrfs_root *root = BTRFS_I(inode)->root;
+       int ret;
+
+       inode_inc_iversion(inode);
+       inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+       /*
+        * We round up to the block size at eof when determining which
+        * extents to clone above, but shouldn't round up the file size.
+        */
+       if (endoff > destoff + olen)
+               endoff = destoff + olen;
+       if (endoff > inode->i_size)
+               btrfs_i_size_write(inode, endoff);
+
+       ret = btrfs_update_inode(trans, root, inode);
+       if (ret) {
+               btrfs_abort_transaction(trans, root, ret);
+               btrfs_end_transaction(trans, root);
+               goto out;
+       }
+       ret = btrfs_end_transaction(trans, root);
+out:
+       return ret;
+}
+
 /**
  * btrfs_clone() - clone a range from inode file to another
  *
@@ -3024,7 +3055,8 @@ out:
  * @destoff: Offset within @inode to start clone
  */
 static int btrfs_clone(struct inode *src, struct inode *inode,
-                      u64 off, u64 olen, u64 olen_aligned, u64 destoff)
+                      const u64 off, const u64 olen, const u64 olen_aligned,
+                      const u64 destoff)
 {
        struct btrfs_root *root = BTRFS_I(inode)->root;
        struct btrfs_path *path = NULL;
@@ -3036,8 +3068,9 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
        int slot;
        int ret;
        int no_quota;
-       u64 len = olen_aligned;
+       const u64 len = olen_aligned;
        u64 last_disko = 0;
+       u64 last_dest_end = destoff;
 
        ret = -ENOMEM;
        buf = vmalloc(btrfs_level_size(root, 0));
@@ -3105,7 +3138,7 @@ process_slot:
                        u64 disko = 0, diskl = 0;
                        u64 datao = 0, datal = 0;
                        u8 comp;
-                       u64 endoff;
+                       u64 drop_start;
 
                        extent = btrfs_item_ptr(leaf, slot,
                                                struct btrfs_file_extent_item);
@@ -3153,6 +3186,18 @@ process_slot:
                        else
                                new_key.offset = destoff;
 
+                       /*
+                        * Deal with a hole that doesn't have an extent item
+                        * that represents it (NO_HOLES feature enabled).
+                        * This hole is either in the middle of the cloning
+                        * range or at the beginning (fully overlaps it or
+                        * partially overlaps it).
+                        */
+                       if (new_key.offset != last_dest_end)
+                               drop_start = last_dest_end;
+                       else
+                               drop_start = new_key.offset;
+
                        /*
                         * 1 - adjusting old extent (we may have to split it)
                         * 1 - add new extent
@@ -3182,7 +3227,7 @@ process_slot:
                                }
 
                                ret = btrfs_drop_extents(trans, root, inode,
-                                                        new_key.offset,
+                                                        drop_start,
                                                         new_key.offset + datal,
                                                         1);
                                if (ret) {
@@ -3283,7 +3328,7 @@ process_slot:
                                aligned_end = ALIGN(new_key.offset + datal,
                                                    root->sectorsize);
                                ret = btrfs_drop_extents(trans, root, inode,
-                                                        new_key.offset,
+                                                        drop_start,
                                                         aligned_end,
                                                         1);
                                if (ret) {
@@ -3321,27 +3366,12 @@ process_slot:
                        btrfs_mark_buffer_dirty(leaf);
                        btrfs_release_path(path);
 
-                       inode_inc_iversion(inode);
-                       inode->i_mtime = inode->i_ctime = CURRENT_TIME;
-
-                       /*
-                        * we round up to the block size at eof when
-                        * determining which extents to clone above,
-                        * but shouldn't round up the file size
-                        */
-                       endoff = new_key.offset + datal;
-                       if (endoff > destoff+olen)
-                               endoff = destoff+olen;
-                       if (endoff > inode->i_size)
-                               btrfs_i_size_write(inode, endoff);
-
-                       ret = btrfs_update_inode(trans, root, inode);
-                       if (ret) {
-                               btrfs_abort_transaction(trans, root, ret);
-                               btrfs_end_transaction(trans, root);
+                       last_dest_end = new_key.offset + datal;
+                       ret = clone_finish_inode_update(trans, inode,
+                                                       last_dest_end,
+                                                       destoff, olen);
+                       if (ret)
                                goto out;
-                       }
-                       ret = btrfs_end_transaction(trans, root);
                        if (new_key.offset + datal >= destoff + len)
                                break;
                }
@@ -3350,6 +3380,34 @@ process_slot:
        }
        ret = 0;
 
+       if (last_dest_end < destoff + len) {
+               /*
+                * We have an implicit hole (NO_HOLES feature is enabled) that
+                * fully or partially overlaps our cloning range at its end.
+                */
+               btrfs_release_path(path);
+
+               /*
+                * 1 - remove extent(s)
+                * 1 - inode update
+                */
+               trans = btrfs_start_transaction(root, 2);
+               if (IS_ERR(trans)) {
+                       ret = PTR_ERR(trans);
+                       goto out;
+               }
+               ret = btrfs_drop_extents(trans, root, inode,
+                                        last_dest_end, destoff + len, 1);
+               if (ret) {
+                       if (ret != -EOPNOTSUPP)
+                               btrfs_abort_transaction(trans, root, ret);
+                       btrfs_end_transaction(trans, root);
+                       goto out;
+               }
+               ret = clone_finish_inode_update(trans, inode, destoff + len,
+                                               destoff, olen);
+       }
+
 out:
        btrfs_free_path(path);
        vfree(buf);