diff mbox series

[v11,13/14] btrfs: send: send compressed extents with encoded writes

Message ID 366f92a7ec5a69dc92290dc2cf6e8603f566495c.1630514529.git.osandov@fb.com (mailing list archive)
State New, archived
Headers show
Series btrfs: add ioctls and send/receive support for reading/writing compressed data | expand

Commit Message

Omar Sandoval Sept. 1, 2021, 5:01 p.m. UTC
From: Omar Sandoval <osandov@fb.com>

Now that all of the pieces are in place, we can use the ENCODED_WRITE
command to send compressed extents when appropriate.

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 fs/btrfs/ctree.h |   4 +
 fs/btrfs/inode.c |   6 +-
 fs/btrfs/send.c  | 230 +++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 220 insertions(+), 20 deletions(-)

Comments

Nikolay Borisov Oct. 18, 2021, 11:59 a.m. UTC | #1
On 1.09.21 г. 20:01, Omar Sandoval wrote:
> From: Omar Sandoval <osandov@fb.com>
> 
> Now that all of the pieces are in place, we can use the ENCODED_WRITE
> command to send compressed extents when appropriate.
> 
> Signed-off-by: Omar Sandoval <osandov@fb.com>

Overall looks sane but consider some of the nits below.


<snip>

> +static int send_encoded_extent(struct send_ctx *sctx, struct btrfs_path *path,
> +			       u64 offset, u64 len)
> +{
> +	struct btrfs_root *root = sctx->send_root;
> +	struct btrfs_fs_info *fs_info = root->fs_info;
> +	struct inode *inode;
> +	struct fs_path *p;
> +	struct extent_buffer *leaf = path->nodes[0];
> +	struct btrfs_key key;
> +	struct btrfs_file_extent_item *ei;
> +	u64 block_start;
> +	u64 block_len;
> +	u32 data_offset;
> +	struct btrfs_cmd_header *hdr;
> +	u32 crc;
> +	int ret;
> +
> +	inode = btrfs_iget(fs_info->sb, sctx->cur_ino, root);
> +	if (IS_ERR(inode))
> +		return PTR_ERR(inode);
> +
> +	p = fs_path_alloc();
> +	if (!p) {
> +		ret = -ENOMEM;
> +		goto out;
> +	}
> +
> +	ret = begin_cmd(sctx, BTRFS_SEND_C_ENCODED_WRITE);
> +	if (ret < 0)
> +		goto out;
> +
> +	ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, p);
> +	if (ret < 0)
> +		goto out;
> +
> +	btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
> +	ei = btrfs_item_ptr(leaf, path->slots[0],
> +			    struct btrfs_file_extent_item);
> +	block_start = btrfs_file_extent_disk_bytenr(leaf, ei);

block_start is somewhat ambiguous here, this is just the disk bytenr of
the extent.

> +	block_len = btrfs_file_extent_disk_num_bytes(leaf, ei);

Why is this called block_len when it's just the size in bytes on-disk?

> +
> +	TLV_PUT_PATH(sctx, BTRFS_SEND_A_PATH, p);
> +	TLV_PUT_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, offset);
> +	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_FILE_LEN,
> +		    min(key.offset + btrfs_file_extent_num_bytes(leaf, ei) - offset,
> +			len));
> +	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_LEN,
> +		    btrfs_file_extent_ram_bytes(leaf, ei));
> +	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_OFFSET,
> +		    offset - key.offset + btrfs_file_extent_offset(leaf, ei));
> +	ret = btrfs_encoded_io_compression_from_extent(
> +				btrfs_file_extent_compression(leaf, ei));
> +	if (ret < 0)
> +		goto out;
> +	TLV_PUT_U32(sctx, BTRFS_SEND_A_COMPRESSION, ret);
> +	TLV_PUT_U32(sctx, BTRFS_SEND_A_ENCRYPTION, 0);
> +
> +	ret = put_data_header(sctx, block_len);
> +	if (ret < 0)
> +		goto out;
> +
> +	data_offset = ALIGN(sctx->send_size, PAGE_SIZE);

nit: The whole data_offset warrants a comment here, since send_buf is
now mapped from send_buf_pages, so all the TLV you've put before are
actually stored in the beginning of send_buf_pages, so by doing the
above you ensure the data write begins on a clean page boundary ...

> +	if (data_offset > sctx->send_max_size ||
> +	    sctx->send_max_size - data_offset < block_len) {
> +		ret = -EOVERFLOW;
> +		goto out;
> +	}
> +
> +	ret = btrfs_encoded_read_regular_fill_pages(inode, block_start,
> +						    block_len,
> +						    sctx->send_buf_pages +
> +						    (data_offset >> PAGE_SHIFT));
> +	if (ret)
> +		goto out;
> +
> +	hdr = (struct btrfs_cmd_header *)sctx->send_buf;
> +	hdr->len = cpu_to_le32(sctx->send_size + block_len - sizeof(*hdr));
> +	hdr->crc = 0;
> +	crc = btrfs_crc32c(0, sctx->send_buf, sctx->send_size);
> +	crc = btrfs_crc32c(crc, sctx->send_buf + data_offset, block_len);

... and because of that you can't simply use send_cmd ;(

> +	hdr->crc = cpu_to_le32(crc);
> +
> +	ret = write_buf(sctx->send_filp, sctx->send_buf, sctx->send_size,
> +			&sctx->send_off);
> +	if (!ret) {
> +		ret = write_buf(sctx->send_filp, sctx->send_buf + data_offset,
> +				block_len, &sctx->send_off);
> +	}
> +	sctx->total_send_size += sctx->send_size + block_len;
> +	sctx->cmd_send_size[le16_to_cpu(hdr->cmd)] +=
> +		sctx->send_size + block_len;
> +	sctx->send_size = 0;
> +
> +tlv_put_failure:
> +out:
> +	fs_path_free(p);
> +	iput(inode);
> +	return ret;
> +}
> +
> +static int send_extent_data(struct send_ctx *sctx, struct btrfs_path *path,
> +			    const u64 offset, const u64 len)

nit: Instead of sending around a btrfs_path struct around and
"polluting" callees to deal with the oddities of our btree interface i.e
btrfs_item_ptr et al. Why not refactor the code so that when we know we
are about to send an extent data simply initialize some struct
extent_info with all the necessary data items: extent type, compression
type, based on the extent type properly initialize a size attribute etc
and pass that. Right now you have send_extent_data fiddling with
path->nodes[0], then based on that you either call
send_encoded_inline_extent or send_encoded_extent, instead pass
extent_info to send_extent_data/clone_range and be done with it.


<snip>
Omar Sandoval Oct. 19, 2021, 12:11 a.m. UTC | #2
On Mon, Oct 18, 2021 at 02:59:08PM +0300, Nikolay Borisov wrote:
> 
> 
> On 1.09.21 г. 20:01, Omar Sandoval wrote:
> > From: Omar Sandoval <osandov@fb.com>
> > 
> > Now that all of the pieces are in place, we can use the ENCODED_WRITE
> > command to send compressed extents when appropriate.
> > 
> > Signed-off-by: Omar Sandoval <osandov@fb.com>
> 
> Overall looks sane but consider some of the nits below.
> 
> 
> <snip>
> 
> > +static int send_encoded_extent(struct send_ctx *sctx, struct btrfs_path *path,
> > +			       u64 offset, u64 len)
> > +{
> > +	struct btrfs_root *root = sctx->send_root;
> > +	struct btrfs_fs_info *fs_info = root->fs_info;
> > +	struct inode *inode;
> > +	struct fs_path *p;
> > +	struct extent_buffer *leaf = path->nodes[0];
> > +	struct btrfs_key key;
> > +	struct btrfs_file_extent_item *ei;
> > +	u64 block_start;
> > +	u64 block_len;
> > +	u32 data_offset;
> > +	struct btrfs_cmd_header *hdr;
> > +	u32 crc;
> > +	int ret;
> > +
> > +	inode = btrfs_iget(fs_info->sb, sctx->cur_ino, root);
> > +	if (IS_ERR(inode))
> > +		return PTR_ERR(inode);
> > +
> > +	p = fs_path_alloc();
> > +	if (!p) {
> > +		ret = -ENOMEM;
> > +		goto out;
> > +	}
> > +
> > +	ret = begin_cmd(sctx, BTRFS_SEND_C_ENCODED_WRITE);
> > +	if (ret < 0)
> > +		goto out;
> > +
> > +	ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, p);
> > +	if (ret < 0)
> > +		goto out;
> > +
> > +	btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
> > +	ei = btrfs_item_ptr(leaf, path->slots[0],
> > +			    struct btrfs_file_extent_item);
> > +	block_start = btrfs_file_extent_disk_bytenr(leaf, ei);
> 
> block_start is somewhat ambiguous here, this is just the disk bytenr of
> the extent.
> 
> > +	block_len = btrfs_file_extent_disk_num_bytes(leaf, ei);
> 
> Why is this called block_len when it's just the size in bytes on-disk?

I copied this naming from extent_map since btrfs_encoded_read() was the
reference for this code, but I'll change the naming here.

> > +
> > +	TLV_PUT_PATH(sctx, BTRFS_SEND_A_PATH, p);
> > +	TLV_PUT_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, offset);
> > +	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_FILE_LEN,
> > +		    min(key.offset + btrfs_file_extent_num_bytes(leaf, ei) - offset,
> > +			len));
> > +	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_LEN,
> > +		    btrfs_file_extent_ram_bytes(leaf, ei));
> > +	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_OFFSET,
> > +		    offset - key.offset + btrfs_file_extent_offset(leaf, ei));
> > +	ret = btrfs_encoded_io_compression_from_extent(
> > +				btrfs_file_extent_compression(leaf, ei));
> > +	if (ret < 0)
> > +		goto out;
> > +	TLV_PUT_U32(sctx, BTRFS_SEND_A_COMPRESSION, ret);
> > +	TLV_PUT_U32(sctx, BTRFS_SEND_A_ENCRYPTION, 0);
> > +
> > +	ret = put_data_header(sctx, block_len);
> > +	if (ret < 0)
> > +		goto out;
> > +
> > +	data_offset = ALIGN(sctx->send_size, PAGE_SIZE);
> 
> nit: The whole data_offset warrants a comment here, since send_buf is
> now mapped from send_buf_pages, so all the TLV you've put before are
> actually stored in the beginning of send_buf_pages, so by doing the
> above you ensure the data write begins on a clean page boundary ...

Yup, I'll add a comment.

> > +	if (data_offset > sctx->send_max_size ||
> > +	    sctx->send_max_size - data_offset < block_len) {
> > +		ret = -EOVERFLOW;
> > +		goto out;
> > +	}
> > +
> > +	ret = btrfs_encoded_read_regular_fill_pages(inode, block_start,
> > +						    block_len,
> > +						    sctx->send_buf_pages +
> > +						    (data_offset >> PAGE_SHIFT));
> > +	if (ret)
> > +		goto out;
> > +
> > +	hdr = (struct btrfs_cmd_header *)sctx->send_buf;
> > +	hdr->len = cpu_to_le32(sctx->send_size + block_len - sizeof(*hdr));
> > +	hdr->crc = 0;
> > +	crc = btrfs_crc32c(0, sctx->send_buf, sctx->send_size);
> > +	crc = btrfs_crc32c(crc, sctx->send_buf + data_offset, block_len);
> 
> ... and because of that you can't simply use send_cmd ;(
> 
> > +	hdr->crc = cpu_to_le32(crc);
> > +
> > +	ret = write_buf(sctx->send_filp, sctx->send_buf, sctx->send_size,
> > +			&sctx->send_off);
> > +	if (!ret) {
> > +		ret = write_buf(sctx->send_filp, sctx->send_buf + data_offset,
> > +				block_len, &sctx->send_off);
> > +	}
> > +	sctx->total_send_size += sctx->send_size + block_len;
> > +	sctx->cmd_send_size[le16_to_cpu(hdr->cmd)] +=
> > +		sctx->send_size + block_len;
> > +	sctx->send_size = 0;
> > +
> > +tlv_put_failure:
> > +out:
> > +	fs_path_free(p);
> > +	iput(inode);
> > +	return ret;
> > +}
> > +
> > +static int send_extent_data(struct send_ctx *sctx, struct btrfs_path *path,
> > +			    const u64 offset, const u64 len)
> 
> nit: Instead of sending around a btrfs_path struct around and
> "polluting" callees to deal with the oddities of our btree interface i.e
> btrfs_item_ptr et al. Why not refactor the code so that when we know we
> are about to send an extent data simply initialize some struct
> extent_info with all the necessary data items: extent type, compression
> type, based on the extent type properly initialize a size attribute etc
> and pass that. Right now you have send_extent_data fiddling with
> path->nodes[0], then based on that you either call
> send_encoded_inline_extent or send_encoded_extent, instead pass
> extent_info to send_extent_data/clone_range and be done with it.

I don't like this for a few reasons:

* An extra "struct extent_info" layer of abstraction would just be extra
  cognitive overhead. I hate having to trace back where the fields in
  some struct came from when it's information that's readily available
  in more well-known data structures.
* send_encoded_inline_extent() (called by send_extent_data()) needs the
  btrfs_path in order to get the inline data anyways.
* clone_range() also already deals with btrfs_paths, so it's not new.
diff mbox series

Patch

diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 7753976bc3c7..caec0422a5c4 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -3223,6 +3223,10 @@  int btrfs_writepage_cow_fixup(struct page *page);
 void btrfs_writepage_endio_finish_ordered(struct btrfs_inode *inode,
 					  struct page *page, u64 start,
 					  u64 end, bool uptodate);
+int btrfs_encoded_io_compression_from_extent(int compress_type);
+int btrfs_encoded_read_regular_fill_pages(struct inode *inode, u64 offset,
+					  u64 disk_io_size,
+					  struct page **pages);
 struct btrfs_ioctl_encoded_io_args;
 ssize_t btrfs_encoded_read(struct kiocb *iocb, struct iov_iter *iter,
 			   struct btrfs_ioctl_encoded_io_args *encoded);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index e751af96c198..6c40419eb2b1 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -10502,7 +10502,7 @@  void btrfs_set_range_writeback(struct btrfs_inode *inode, u64 start, u64 end)
 	}
 }
 
-static int btrfs_encoded_io_compression_from_extent(int compress_type)
+int btrfs_encoded_io_compression_from_extent(int compress_type)
 {
 	switch (compress_type) {
 	case BTRFS_COMPRESS_NONE:
@@ -10705,8 +10705,8 @@  static void btrfs_encoded_read_endio(struct bio *bio)
 	bio_put(bio);
 }
 
-static int btrfs_encoded_read_regular_fill_pages(struct inode *inode, u64 offset,
-						 u64 disk_io_size, struct page **pages)
+int btrfs_encoded_read_regular_fill_pages(struct inode *inode, u64 offset,
+					  u64 disk_io_size, struct page **pages)
 {
 	struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
 	struct btrfs_encoded_read_private priv = {
diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index a000efe2658a..0ba8dc3a9f56 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -595,6 +595,7 @@  static int tlv_put(struct send_ctx *sctx, u16 attr, const void *data, int len)
 		return tlv_put(sctx, attr, &__tmp, sizeof(__tmp));	\
 	}
 
+TLV_PUT_DEFINE_INT(32)
 TLV_PUT_DEFINE_INT(64)
 
 static int tlv_put_string(struct send_ctx *sctx, u16 attr,
@@ -5203,16 +5204,211 @@  static int send_hole(struct send_ctx *sctx, u64 end)
 	return ret;
 }
 
-static int send_extent_data(struct send_ctx *sctx,
-			    const u64 offset,
-			    const u64 len)
+static int send_encoded_inline_extent(struct send_ctx *sctx,
+				      struct btrfs_path *path, u64 offset,
+				      u64 len)
 {
+	struct btrfs_root *root = sctx->send_root;
+	struct btrfs_fs_info *fs_info = root->fs_info;
+	struct inode *inode;
+	struct fs_path *p;
+	struct extent_buffer *leaf = path->nodes[0];
+	struct btrfs_key key;
+	struct btrfs_file_extent_item *ei;
+	u64 ram_bytes;
+	size_t inline_size;
+	int ret;
+
+	inode = btrfs_iget(fs_info->sb, sctx->cur_ino, root);
+	if (IS_ERR(inode))
+		return PTR_ERR(inode);
+
+	p = fs_path_alloc();
+	if (!p) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = begin_cmd(sctx, BTRFS_SEND_C_ENCODED_WRITE);
+	if (ret < 0)
+		goto out;
+
+	ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, p);
+	if (ret < 0)
+		goto out;
+
+	btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
+	ei = btrfs_item_ptr(leaf, path->slots[0],
+			    struct btrfs_file_extent_item);
+	ram_bytes = btrfs_file_extent_ram_bytes(leaf, ei);
+	inline_size = btrfs_file_extent_inline_item_len(leaf,
+						btrfs_item_nr(path->slots[0]));
+
+	TLV_PUT_PATH(sctx, BTRFS_SEND_A_PATH, p);
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, offset);
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_FILE_LEN,
+		    min(key.offset + ram_bytes - offset, len));
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_LEN, ram_bytes);
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_OFFSET, offset - key.offset);
+	ret = btrfs_encoded_io_compression_from_extent(
+				btrfs_file_extent_compression(leaf, ei));
+	if (ret < 0)
+		goto out;
+	TLV_PUT_U32(sctx, BTRFS_SEND_A_COMPRESSION, ret);
+	TLV_PUT_U32(sctx, BTRFS_SEND_A_ENCRYPTION, 0);
+
+	ret = put_data_header(sctx, inline_size);
+	if (ret < 0)
+		goto out;
+	read_extent_buffer(leaf, sctx->send_buf + sctx->send_size,
+			   btrfs_file_extent_inline_start(ei), inline_size);
+	sctx->send_size += inline_size;
+
+	ret = send_cmd(sctx);
+
+tlv_put_failure:
+out:
+	fs_path_free(p);
+	iput(inode);
+	return ret;
+}
+
+static int send_encoded_extent(struct send_ctx *sctx, struct btrfs_path *path,
+			       u64 offset, u64 len)
+{
+	struct btrfs_root *root = sctx->send_root;
+	struct btrfs_fs_info *fs_info = root->fs_info;
+	struct inode *inode;
+	struct fs_path *p;
+	struct extent_buffer *leaf = path->nodes[0];
+	struct btrfs_key key;
+	struct btrfs_file_extent_item *ei;
+	u64 block_start;
+	u64 block_len;
+	u32 data_offset;
+	struct btrfs_cmd_header *hdr;
+	u32 crc;
+	int ret;
+
+	inode = btrfs_iget(fs_info->sb, sctx->cur_ino, root);
+	if (IS_ERR(inode))
+		return PTR_ERR(inode);
+
+	p = fs_path_alloc();
+	if (!p) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = begin_cmd(sctx, BTRFS_SEND_C_ENCODED_WRITE);
+	if (ret < 0)
+		goto out;
+
+	ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, p);
+	if (ret < 0)
+		goto out;
+
+	btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
+	ei = btrfs_item_ptr(leaf, path->slots[0],
+			    struct btrfs_file_extent_item);
+	block_start = btrfs_file_extent_disk_bytenr(leaf, ei);
+	block_len = btrfs_file_extent_disk_num_bytes(leaf, ei);
+
+	TLV_PUT_PATH(sctx, BTRFS_SEND_A_PATH, p);
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, offset);
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_FILE_LEN,
+		    min(key.offset + btrfs_file_extent_num_bytes(leaf, ei) - offset,
+			len));
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_LEN,
+		    btrfs_file_extent_ram_bytes(leaf, ei));
+	TLV_PUT_U64(sctx, BTRFS_SEND_A_UNENCODED_OFFSET,
+		    offset - key.offset + btrfs_file_extent_offset(leaf, ei));
+	ret = btrfs_encoded_io_compression_from_extent(
+				btrfs_file_extent_compression(leaf, ei));
+	if (ret < 0)
+		goto out;
+	TLV_PUT_U32(sctx, BTRFS_SEND_A_COMPRESSION, ret);
+	TLV_PUT_U32(sctx, BTRFS_SEND_A_ENCRYPTION, 0);
+
+	ret = put_data_header(sctx, block_len);
+	if (ret < 0)
+		goto out;
+
+	data_offset = ALIGN(sctx->send_size, PAGE_SIZE);
+	if (data_offset > sctx->send_max_size ||
+	    sctx->send_max_size - data_offset < block_len) {
+		ret = -EOVERFLOW;
+		goto out;
+	}
+
+	ret = btrfs_encoded_read_regular_fill_pages(inode, block_start,
+						    block_len,
+						    sctx->send_buf_pages +
+						    (data_offset >> PAGE_SHIFT));
+	if (ret)
+		goto out;
+
+	hdr = (struct btrfs_cmd_header *)sctx->send_buf;
+	hdr->len = cpu_to_le32(sctx->send_size + block_len - sizeof(*hdr));
+	hdr->crc = 0;
+	crc = btrfs_crc32c(0, sctx->send_buf, sctx->send_size);
+	crc = btrfs_crc32c(crc, sctx->send_buf + data_offset, block_len);
+	hdr->crc = cpu_to_le32(crc);
+
+	ret = write_buf(sctx->send_filp, sctx->send_buf, sctx->send_size,
+			&sctx->send_off);
+	if (!ret) {
+		ret = write_buf(sctx->send_filp, sctx->send_buf + data_offset,
+				block_len, &sctx->send_off);
+	}
+	sctx->total_send_size += sctx->send_size + block_len;
+	sctx->cmd_send_size[le16_to_cpu(hdr->cmd)] +=
+		sctx->send_size + block_len;
+	sctx->send_size = 0;
+
+tlv_put_failure:
+out:
+	fs_path_free(p);
+	iput(inode);
+	return ret;
+}
+
+static int send_extent_data(struct send_ctx *sctx, struct btrfs_path *path,
+			    const u64 offset, const u64 len)
+{
+	struct extent_buffer *leaf = path->nodes[0];
+	struct btrfs_file_extent_item *ei;
 	u64 read_size = max_send_read_size(sctx);
 	u64 sent = 0;
 
 	if (sctx->flags & BTRFS_SEND_FLAG_NO_FILE_DATA)
 		return send_update_extent(sctx, offset, len);
 
+	ei = btrfs_item_ptr(leaf, path->slots[0],
+			    struct btrfs_file_extent_item);
+	if ((sctx->flags & BTRFS_SEND_FLAG_COMPRESSED) &&
+	    btrfs_file_extent_compression(leaf, ei) != BTRFS_COMPRESS_NONE) {
+		bool is_inline = (btrfs_file_extent_type(leaf, ei) ==
+				  BTRFS_FILE_EXTENT_INLINE);
+
+		/*
+		 * Send the compressed extent unless the compressed data is
+		 * larger than the decompressed data. This can happen if we're
+		 * not sending the entire extent, either because it has been
+		 * partially overwritten/truncated or because this is a part of
+		 * the extent that we couldn't clone in clone_range().
+		 */
+		if (is_inline &&
+		    btrfs_file_extent_inline_item_len(leaf,
+					btrfs_item_nr(path->slots[0])) <= len) {
+			return send_encoded_inline_extent(sctx, path, offset,
+							  len);
+		} else if (!is_inline &&
+			   btrfs_file_extent_disk_num_bytes(leaf, ei) <= len) {
+			return send_encoded_extent(sctx, path, offset, len);
+		}
+	}
+
 	while (sent < len) {
 		u64 size = min(len - sent, read_size);
 		int ret;
@@ -5283,12 +5479,9 @@  static int send_capabilities(struct send_ctx *sctx)
 	return ret;
 }
 
-static int clone_range(struct send_ctx *sctx,
-		       struct clone_root *clone_root,
-		       const u64 disk_byte,
-		       u64 data_offset,
-		       u64 offset,
-		       u64 len)
+static int clone_range(struct send_ctx *sctx, struct btrfs_path *dst_path,
+		       struct clone_root *clone_root, const u64 disk_byte,
+		       u64 data_offset, u64 offset, u64 len)
 {
 	struct btrfs_path *path;
 	struct btrfs_key key;
@@ -5312,7 +5505,7 @@  static int clone_range(struct send_ctx *sctx,
 	 */
 	if (clone_root->offset == 0 &&
 	    len == sctx->send_root->fs_info->sectorsize)
-		return send_extent_data(sctx, offset, len);
+		return send_extent_data(sctx, dst_path, offset, len);
 
 	path = alloc_path_for_send();
 	if (!path)
@@ -5409,7 +5602,8 @@  static int clone_range(struct send_ctx *sctx,
 
 			if (hole_len > len)
 				hole_len = len;
-			ret = send_extent_data(sctx, offset, hole_len);
+			ret = send_extent_data(sctx, dst_path, offset,
+					       hole_len);
 			if (ret < 0)
 				goto out;
 
@@ -5482,14 +5676,16 @@  static int clone_range(struct send_ctx *sctx,
 					if (ret < 0)
 						goto out;
 				}
-				ret = send_extent_data(sctx, offset + slen,
+				ret = send_extent_data(sctx, dst_path,
+						       offset + slen,
 						       clone_len - slen);
 			} else {
 				ret = send_clone(sctx, offset, clone_len,
 						 clone_root);
 			}
 		} else {
-			ret = send_extent_data(sctx, offset, clone_len);
+			ret = send_extent_data(sctx, dst_path, offset,
+					       clone_len);
 		}
 
 		if (ret < 0)
@@ -5521,7 +5717,7 @@  static int clone_range(struct send_ctx *sctx,
 	}
 
 	if (len > 0)
-		ret = send_extent_data(sctx, offset, len);
+		ret = send_extent_data(sctx, dst_path, offset, len);
 	else
 		ret = 0;
 out:
@@ -5552,10 +5748,10 @@  static int send_write_or_clone(struct send_ctx *sctx,
 				    struct btrfs_file_extent_item);
 		disk_byte = btrfs_file_extent_disk_bytenr(path->nodes[0], ei);
 		data_offset = btrfs_file_extent_offset(path->nodes[0], ei);
-		ret = clone_range(sctx, clone_root, disk_byte, data_offset,
-				  offset, end - offset);
+		ret = clone_range(sctx, path, clone_root, disk_byte,
+				  data_offset, offset, end - offset);
 	} else {
-		ret = send_extent_data(sctx, offset, end - offset);
+		ret = send_extent_data(sctx, path, offset, end - offset);
 	}
 	sctx->cur_inode_next_write_offset = end;
 	return ret;