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