All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] btrfs: send: fix missing hole punching during incremental send
@ 2026-10-21 20:17 fdmanana
  2026-10-21 21:41 ` [PATCH v2] " fdmanana
  2026-10-23  8:51 ` [PATCH v3] " fdmanana
  0 siblings, 2 replies; 6+ messages in thread
From: fdmanana @ 2026-10-21 20:17 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

When using the no-holes feature (enabled by default since btrfs-progs
v5.15) and doing an incremental send, we can miss issuing a hole punching
command when the following conditions happen:

1) A hole is punched in a tail section of a file extent item that happens
   to be the last item in a leaf (for example the extent covers the file
   range [1M, 3M) and a hole is punched in the range [2M, 3M)). Between
   the end of this file extent item's range and the i_size, there are
   other file extent items;

2) The next leaf was not modified in the send snapshot, remaining shared
   with the parent snapshot.

In this scenario we are unable to detect a hole was punched and never
issue a hole punch command because with no-holes there we don't have an
exlicit file extent item to mark a hole.

Fix this by teaching maybe_send_hole() to detect such case.
A test case for fstests will follow soon.

Reported-by: Tristan Elvers <tristanelvers@gmail.com>
Link: https://lore.kernel.org/linux-btrfs/CAFoLKK+aErV4p6ZJPnVeH5SHQ5++0f=8ZgxPWe-E-CkeKBs8PA@mail.gmail.com/
Fixes: 16e7549f045d ("Btrfs: incompatible format change to remove hole extents")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/send.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index c523bf950c89..3a4d86af9ee2 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -6371,6 +6371,11 @@ static int range_is_hole_in_parent(struct send_ctx *sctx,
 static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
 			   struct btrfs_key *key)
 {
+	BTRFS_PATH_AUTO_FREE(next_path);
+	struct btrfs_file_extent_item *ei;
+	u64 num_bytes;
+	u64 ram_bytes;
+	u64 next_offset;
 	int ret = 0;
 
 	if (sctx->cur_ino != key->objectid || !need_send_hole(sctx))
@@ -6405,6 +6410,66 @@ static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
 			ret = 0;
 	}
 	sctx->cur_inode_last_extent = btrfs_file_extent_end(path);
+
+	/*
+	 * If we are the last file extent item in a leaf, we may have had a tail
+	 * section that got a hole punched, and if there is no change in the
+	 * next leaf, we must find out if we have such a hole and punch it.
+	 */
+	if (path->slots[0] != btrfs_header_nritems(path->nodes[0]) - 1)
+		return ret;
+
+	ei = btrfs_item_ptr(path->nodes[0], path->slots[0],
+			    struct btrfs_file_extent_item);
+	num_bytes = btrfs_file_extent_num_bytes(path->nodes[0], ei);
+	ram_bytes = btrfs_file_extent_ram_bytes(path->nodes[0], ei);
+	if (num_bytes == ram_bytes)
+		return ret;
+
+	next_path = alloc_path_for_send();
+	if (!next_path)
+		return -ENOMEM;
+
+	ret = btrfs_search_slot(NULL, sctx->send_root, key, next_path, 0, 0);
+	if (ret < 0)
+		return ret;
+	if (WARN_ON(ret != 0))
+		return -EUCLEAN;
+
+	ret = btrfs_next_leaf(sctx->send_root, next_path);
+	if (ret < 0) {
+		return ret;
+	} else if (ret > 0) {
+		/*
+		 * No next leaf, must punch hole to i_size and set the path back
+		 * to where it was before.
+		 */
+		next_offset = sctx->cur_inode_size;
+	} else {
+		struct btrfs_key next_key;
+
+		btrfs_item_key_to_cpu(next_path->nodes[0], &next_key,
+				      next_path->slots[0]);
+		if (next_key.objectid != key->objectid ||
+		    next_key.type != BTRFS_EXTENT_DATA_KEY)
+			next_offset = sctx->cur_inode_size;
+		else
+			next_offset = next_key.offset;
+	}
+
+	btrfs_free_path(next_path);
+	next_path = NULL;
+
+	ret = range_is_hole_in_parent(sctx, sctx->cur_inode_last_extent, next_offset);
+	if (ret < 0)
+		return ret;
+	else if (ret == 0)
+		ret = send_hole(sctx, next_offset);
+	else
+		ret = 0;
+
+	sctx->cur_inode_last_extent = next_offset;
+
 	return ret;
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v2] btrfs: send: fix missing hole punching during incremental send
  2026-10-21 20:17 [PATCH] btrfs: send: fix missing hole punching during incremental send fdmanana
@ 2026-10-21 21:41 ` fdmanana
  2026-10-23  6:39   ` Qu Wenruo
  2026-10-23  8:51 ` [PATCH v3] " fdmanana
  1 sibling, 1 reply; 6+ messages in thread
From: fdmanana @ 2026-10-21 21:41 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

When using the no-holes feature (enabled by default since btrfs-progs
v5.15) and doing an incremental send, we can miss issuing a hole punching
command when the following conditions happen:

1) A hole is punched in a tail section of a file extent item that happens
   to be the last item in a leaf (for example the extent covers the file
   range [1M, 3M) and a hole is punched in the range [2M, 3M)). Between
   the end of this file extent item's range and the i_size, there are
   other file extent items;

2) The next leaf was not modified in the send snapshot, remaining shared
   with the parent snapshot.

In this scenario we are unable to detect a hole was punched and never
issue a hole punch command because with no-holes there we don't have an
exlicit file extent item to mark a hole.

Fix this by teaching maybe_send_hole() to detect such case.
A test case for fstests will follow soon.

Reported-by: Tristan Elvers <tristanelvers@gmail.com>
Link: https://lore.kernel.org/linux-btrfs/CAFoLKK+aErV4p6ZJPnVeH5SHQ5++0f=8ZgxPWe-E-CkeKBs8PA@mail.gmail.com/
Fixes: 16e7549f045d ("Btrfs: incompatible format change to remove hole extents")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---

V2: Address a couple issues pointed by sashiko, simplify a few things.

 fs/btrfs/send.c | 62 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 58 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index c523bf950c89..cec1aae5ce14 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -6371,6 +6371,8 @@ static int range_is_hole_in_parent(struct send_ctx *sctx,
 static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
 			   struct btrfs_key *key)
 {
+	BTRFS_PATH_AUTO_FREE(next_path);
+	u64 next_offset;
 	int ret = 0;
 
 	if (sctx->cur_ino != key->objectid || !need_send_hole(sctx))
@@ -6397,14 +6399,66 @@ static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
 		ret = range_is_hole_in_parent(sctx,
 					      sctx->cur_inode_last_extent,
 					      key->offset);
-		if (ret < 0)
-			return ret;
-		else if (ret == 0)
+		if (ret == 0)
 			ret = send_hole(sctx, key->offset);
-		else
+		else if (ret == 1)
 			ret = 0;
+
+		if (ret < 0)
+			return ret;
 	}
 	sctx->cur_inode_last_extent = btrfs_file_extent_end(path);
+
+	/*
+	 * If we are the last file extent item in a leaf, we may have had a tail
+	 * section that got a hole punched, and if there is no change in the
+	 * next leaf, we must find out if we have such a hole and punch it.
+	 */
+	if (path->slots[0] != btrfs_header_nritems(path->nodes[0]) - 1)
+		return ret;
+
+	next_path = alloc_path_for_send();
+	if (!next_path)
+		return -ENOMEM;
+
+	ret = btrfs_search_slot(NULL, sctx->send_root, key, next_path, 0, 0);
+	if (ret < 0)
+		return ret;
+	if (WARN_ON(ret != 0))
+		return -EUCLEAN;
+
+	ret = btrfs_next_leaf(sctx->send_root, next_path);
+	if (ret < 0) {
+		return ret;
+	} else if (ret > 0) {
+		/*
+		 * No next leaf, must punch hole to i_size and set the path back
+		 * to where it was before.
+		 */
+		next_offset = sctx->cur_inode_size;
+	} else {
+		struct btrfs_key next_key;
+
+		btrfs_item_key_to_cpu(next_path->nodes[0], &next_key,
+				      next_path->slots[0]);
+		if (next_key.objectid != key->objectid ||
+		    next_key.type != BTRFS_EXTENT_DATA_KEY)
+			next_offset = sctx->cur_inode_size;
+		else
+			next_offset = next_key.offset;
+	}
+
+	btrfs_free_path(next_path);
+	next_path = NULL;
+
+	ret = range_is_hole_in_parent(sctx, sctx->cur_inode_last_extent, next_offset);
+	if (ret == 0)
+		ret = send_hole(sctx, next_offset);
+	else if (ret == 1)
+		ret = 0;
+
+	sctx->cur_inode_last_extent = next_offset;
+
 	return ret;
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] btrfs: send: fix missing hole punching during incremental send
  2026-10-21 21:41 ` [PATCH v2] " fdmanana
@ 2026-10-23  6:39   ` Qu Wenruo
  2026-10-23  8:51     ` Filipe Manana
  0 siblings, 1 reply; 6+ messages in thread
From: Qu Wenruo @ 2026-10-23  6:39 UTC (permalink / raw)
  To: fdmanana, linux-btrfs



在 2026/10/22 08:11, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
> 
> When using the no-holes feature (enabled by default since btrfs-progs
> v5.15) and doing an incremental send, we can miss issuing a hole punching
> command when the following conditions happen:
> 
> 1) A hole is punched in a tail section of a file extent item that happens
>     to be the last item in a leaf (for example the extent covers the file
>     range [1M, 3M) and a hole is punched in the range [2M, 3M)). Between
>     the end of this file extent item's range and the i_size, there are
>     other file extent items;
> 
> 2) The next leaf was not modified in the send snapshot, remaining shared
>     with the parent snapshot.
> 
> In this scenario we are unable to detect a hole was punched and never
> issue a hole punch command because with no-holes there we don't have an
> exlicit file extent item to mark a hole.
> 
> Fix this by teaching maybe_send_hole() to detect such case.
> A test case for fstests will follow soon.
> 
> Reported-by: Tristan Elvers <tristanelvers@gmail.com>
> Link: https://lore.kernel.org/linux-btrfs/CAFoLKK+aErV4p6ZJPnVeH5SHQ5++0f=8ZgxPWe-E-CkeKBs8PA@mail.gmail.com/
> Fixes: 16e7549f045d ("Btrfs: incompatible format change to remove hole extents")
> Signed-off-by: Filipe Manana <fdmanana@suse.com>
> ---
> 
> V2: Address a couple issues pointed by sashiko, simplify a few things.
> 
>   fs/btrfs/send.c | 62 +++++++++++++++++++++++++++++++++++++++++++++----
>   1 file changed, 58 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
> index c523bf950c89..cec1aae5ce14 100644
> --- a/fs/btrfs/send.c
> +++ b/fs/btrfs/send.c
> @@ -6371,6 +6371,8 @@ static int range_is_hole_in_parent(struct send_ctx *sctx,
>   static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
>   			   struct btrfs_key *key)
>   {
> +	BTRFS_PATH_AUTO_FREE(next_path);
> +	u64 next_offset;
>   	int ret = 0;
>   
>   	if (sctx->cur_ino != key->objectid || !need_send_hole(sctx))
> @@ -6397,14 +6399,66 @@ static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
>   		ret = range_is_hole_in_parent(sctx,
>   					      sctx->cur_inode_last_extent,
>   					      key->offset);
> -		if (ret < 0)
> -			return ret;
> -		else if (ret == 0)
> +		if (ret == 0)
>   			ret = send_hole(sctx, key->offset);
> -		else
> +		else if (ret == 1)
>   			ret = 0;
> +
> +		if (ret < 0)
> +			return ret;
>   	}
>   	sctx->cur_inode_last_extent = btrfs_file_extent_end(path);
> +
> +	/*
> +	 * If we are the last file extent item in a leaf, we may have had a tail
> +	 * section that got a hole punched, and if there is no change in the
> +	 * next leaf, we must find out if we have such a hole and punch it.
> +	 */
> +	if (path->slots[0] != btrfs_header_nritems(path->nodes[0]) - 1)
> +		return ret;

Just a nitpick, do we need to check NO_HOLES features? IIRC without 
NO_HOLES, we should always have a file extent covering i_size, thus 
without NO_HOLES, we should not need to check for the next leaf?

Thanks,
Qu

> +
> +	next_path = alloc_path_for_send();
> +	if (!next_path)
> +		return -ENOMEM;
> +
> +	ret = btrfs_search_slot(NULL, sctx->send_root, key, next_path, 0, 0);
> +	if (ret < 0)
> +		return ret;
> +	if (WARN_ON(ret != 0))
> +		return -EUCLEAN;
> +
> +	ret = btrfs_next_leaf(sctx->send_root, next_path);
> +	if (ret < 0) {
> +		return ret;
> +	} else if (ret > 0) {
> +		/*
> +		 * No next leaf, must punch hole to i_size and set the path back
> +		 * to where it was before.
> +		 */
> +		next_offset = sctx->cur_inode_size;
> +	} else {
> +		struct btrfs_key next_key;
> +
> +		btrfs_item_key_to_cpu(next_path->nodes[0], &next_key,
> +				      next_path->slots[0]);
> +		if (next_key.objectid != key->objectid ||
> +		    next_key.type != BTRFS_EXTENT_DATA_KEY)
> +			next_offset = sctx->cur_inode_size;
> +		else
> +			next_offset = next_key.offset;
> +	}
> +
> +	btrfs_free_path(next_path);
> +	next_path = NULL;
> +
> +	ret = range_is_hole_in_parent(sctx, sctx->cur_inode_last_extent, next_offset);
> +	if (ret == 0)
> +		ret = send_hole(sctx, next_offset);
> +	else if (ret == 1)
> +		ret = 0;
> +
> +	sctx->cur_inode_last_extent = next_offset;
> +
>   	return ret;
>   }
>   


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] btrfs: send: fix missing hole punching during incremental send
  2026-10-23  6:39   ` Qu Wenruo
@ 2026-10-23  8:51     ` Filipe Manana
  0 siblings, 0 replies; 6+ messages in thread
From: Filipe Manana @ 2026-10-23  8:51 UTC (permalink / raw)
  To: Qu Wenruo; +Cc: linux-btrfs

On Fri, Oct 23, 2026 at 7:39 AM Qu Wenruo <quwenruo.btrfs@gmx.com> wrote:
>
>
>
> 在 2026/10/22 08:11, fdmanana@kernel.org 写道:
> > From: Filipe Manana <fdmanana@suse.com>
> >
> > When using the no-holes feature (enabled by default since btrfs-progs
> > v5.15) and doing an incremental send, we can miss issuing a hole punching
> > command when the following conditions happen:
> >
> > 1) A hole is punched in a tail section of a file extent item that happens
> >     to be the last item in a leaf (for example the extent covers the file
> >     range [1M, 3M) and a hole is punched in the range [2M, 3M)). Between
> >     the end of this file extent item's range and the i_size, there are
> >     other file extent items;
> >
> > 2) The next leaf was not modified in the send snapshot, remaining shared
> >     with the parent snapshot.
> >
> > In this scenario we are unable to detect a hole was punched and never
> > issue a hole punch command because with no-holes there we don't have an
> > exlicit file extent item to mark a hole.
> >
> > Fix this by teaching maybe_send_hole() to detect such case.
> > A test case for fstests will follow soon.
> >
> > Reported-by: Tristan Elvers <tristanelvers@gmail.com>
> > Link: https://lore.kernel.org/linux-btrfs/CAFoLKK+aErV4p6ZJPnVeH5SHQ5++0f=8ZgxPWe-E-CkeKBs8PA@mail.gmail.com/
> > Fixes: 16e7549f045d ("Btrfs: incompatible format change to remove hole extents")
> > Signed-off-by: Filipe Manana <fdmanana@suse.com>
> > ---
> >
> > V2: Address a couple issues pointed by sashiko, simplify a few things.
> >
> >   fs/btrfs/send.c | 62 +++++++++++++++++++++++++++++++++++++++++++++----
> >   1 file changed, 58 insertions(+), 4 deletions(-)
> >
> > diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
> > index c523bf950c89..cec1aae5ce14 100644
> > --- a/fs/btrfs/send.c
> > +++ b/fs/btrfs/send.c
> > @@ -6371,6 +6371,8 @@ static int range_is_hole_in_parent(struct send_ctx *sctx,
> >   static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
> >                          struct btrfs_key *key)
> >   {
> > +     BTRFS_PATH_AUTO_FREE(next_path);
> > +     u64 next_offset;
> >       int ret = 0;
> >
> >       if (sctx->cur_ino != key->objectid || !need_send_hole(sctx))
> > @@ -6397,14 +6399,66 @@ static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
> >               ret = range_is_hole_in_parent(sctx,
> >                                             sctx->cur_inode_last_extent,
> >                                             key->offset);
> > -             if (ret < 0)
> > -                     return ret;
> > -             else if (ret == 0)
> > +             if (ret == 0)
> >                       ret = send_hole(sctx, key->offset);
> > -             else
> > +             else if (ret == 1)
> >                       ret = 0;
> > +
> > +             if (ret < 0)
> > +                     return ret;
> >       }
> >       sctx->cur_inode_last_extent = btrfs_file_extent_end(path);
> > +
> > +     /*
> > +      * If we are the last file extent item in a leaf, we may have had a tail
> > +      * section that got a hole punched, and if there is no change in the
> > +      * next leaf, we must find out if we have such a hole and punch it.
> > +      */
> > +     if (path->slots[0] != btrfs_header_nritems(path->nodes[0]) - 1)
> > +             return ret;
>
> Just a nitpick, do we need to check NO_HOLES features? IIRC without
> NO_HOLES, we should always have a file extent covering i_size, thus
> without NO_HOLES, we should not need to check for the next leaf?

It could check that, but NO_HOLES is enabled by default for quite some
years now, plus skipped it for consistency with the existing code that
is exclusive for NO_HOLES but also misses the check.
Since I have another optimization to skip that check and sending empty
holes, I'll include it in the next version.

Thanks.

>
> Thanks,
> Qu
>
> > +
> > +     next_path = alloc_path_for_send();
> > +     if (!next_path)
> > +             return -ENOMEM;
> > +
> > +     ret = btrfs_search_slot(NULL, sctx->send_root, key, next_path, 0, 0);
> > +     if (ret < 0)
> > +             return ret;
> > +     if (WARN_ON(ret != 0))
> > +             return -EUCLEAN;
> > +
> > +     ret = btrfs_next_leaf(sctx->send_root, next_path);
> > +     if (ret < 0) {
> > +             return ret;
> > +     } else if (ret > 0) {
> > +             /*
> > +              * No next leaf, must punch hole to i_size and set the path back
> > +              * to where it was before.
> > +              */
> > +             next_offset = sctx->cur_inode_size;
> > +     } else {
> > +             struct btrfs_key next_key;
> > +
> > +             btrfs_item_key_to_cpu(next_path->nodes[0], &next_key,
> > +                                   next_path->slots[0]);
> > +             if (next_key.objectid != key->objectid ||
> > +                 next_key.type != BTRFS_EXTENT_DATA_KEY)
> > +                     next_offset = sctx->cur_inode_size;
> > +             else
> > +                     next_offset = next_key.offset;
> > +     }
> > +
> > +     btrfs_free_path(next_path);
> > +     next_path = NULL;
> > +
> > +     ret = range_is_hole_in_parent(sctx, sctx->cur_inode_last_extent, next_offset);
> > +     if (ret == 0)
> > +             ret = send_hole(sctx, next_offset);
> > +     else if (ret == 1)
> > +             ret = 0;
> > +
> > +     sctx->cur_inode_last_extent = next_offset;
> > +
> >       return ret;
> >   }
> >
>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v3] btrfs: send: fix missing hole punching during incremental send
  2026-10-21 20:17 [PATCH] btrfs: send: fix missing hole punching during incremental send fdmanana
  2026-10-21 21:41 ` [PATCH v2] " fdmanana
@ 2026-10-23  8:51 ` fdmanana
  2026-10-23  9:21   ` Qu Wenruo
  1 sibling, 1 reply; 6+ messages in thread
From: fdmanana @ 2026-10-23  8:51 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

When using the no-holes feature (enabled by default since btrfs-progs
v5.15) and doing an incremental send, we can miss issuing a hole punching
command when the following conditions happen:

1) A hole is punched in a tail section of a file extent item that happens
   to be the last item in a leaf (for example the extent covers the file
   range [1M, 3M) and a hole is punched in the range [2M, 3M)). Between
   the end of this file extent item's range and the i_size, there are
   other file extent items;

2) The next leaf was not modified in the send snapshot, remaining shared
   with the parent snapshot.

In this scenario we are unable to detect a hole was punched and never
issue a hole punch command because with no-holes there we don't have an
exlicit file extent item to mark a hole.

Fix this by teaching maybe_send_hole() to detect such case.
A test case for fstests will follow soon.

Reported-by: Tristan Elvers <tristanelvers@gmail.com>
Link: https://lore.kernel.org/linux-btrfs/CAFoLKK+aErV4p6ZJPnVeH5SHQ5++0f=8ZgxPWe-E-CkeKBs8PA@mail.gmail.com/
Fixes: 16e7549f045d ("Btrfs: incompatible format change to remove hole extents")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---

V3: Avoid tree searches in case the i_size is in the last file extent
    item and in case there is no hole between the last file extent
    item and the first one in the next leaf.

V2: Address a couple issues pointed by sashiko, simplify a few things.

 fs/btrfs/send.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 65 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index c523bf950c89..1b903e75cec0 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -6371,6 +6371,9 @@ static int range_is_hole_in_parent(struct send_ctx *sctx,
 static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
 			   struct btrfs_key *key)
 {
+	BTRFS_PATH_AUTO_FREE(next_path);
+	const u64 cur_extent_end = btrfs_file_extent_end(path);
+	u64 next_offset;
 	int ret = 0;
 
 	if (sctx->cur_ino != key->objectid || !need_send_hole(sctx))
@@ -6397,14 +6400,72 @@ static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
 		ret = range_is_hole_in_parent(sctx,
 					      sctx->cur_inode_last_extent,
 					      key->offset);
+		if (ret == 0)
+			ret = send_hole(sctx, key->offset);
+		else if (ret == 1)
+			ret = 0;
+
 		if (ret < 0)
 			return ret;
-		else if (ret == 0)
-			ret = send_hole(sctx, key->offset);
+	}
+	sctx->cur_inode_last_extent = cur_extent_end;
+
+	/*
+	 * If we are the last file extent item in a leaf, we may have had a tail
+	 * section that got a hole punched or the extent replaced with a smaller
+	 * one, and if there is no change in the next leaf, we must find out if
+	 * we have such a hole and punch it.
+	 */
+	if (!btrfs_fs_incompat(sctx->send_root->fs_info, NO_HOLES) ||
+	    (sctx->cur_inode_size <= cur_extent_end) ||
+	    (path->slots[0] != btrfs_header_nritems(path->nodes[0]) - 1))
+		return 0;
+
+	next_path = alloc_path_for_send();
+	if (!next_path)
+		return -ENOMEM;
+
+	ret = btrfs_search_slot(NULL, sctx->send_root, key, next_path, 0, 0);
+	if (ret < 0)
+		return ret;
+	if (WARN_ON(ret != 0))
+		return -EUCLEAN;
+
+	ret = btrfs_next_leaf(sctx->send_root, next_path);
+	if (ret < 0) {
+		return ret;
+	} else if (ret > 0) {
+		/*
+		 * No next leaf, must punch hole to i_size and set the path back
+		 * to where it was before.
+		 */
+		next_offset = sctx->cur_inode_size;
+	} else {
+		struct btrfs_key next_key;
+
+		btrfs_item_key_to_cpu(next_path->nodes[0], &next_key,
+				      next_path->slots[0]);
+		if (next_key.objectid != key->objectid ||
+		    next_key.type != BTRFS_EXTENT_DATA_KEY)
+			next_offset = sctx->cur_inode_size;
 		else
-			ret = 0;
+			next_offset = next_key.offset;
 	}
-	sctx->cur_inode_last_extent = btrfs_file_extent_end(path);
+
+	if (next_offset == cur_extent_end)
+		return 0;
+
+	btrfs_free_path(next_path);
+	next_path = NULL;
+
+	ret = range_is_hole_in_parent(sctx, sctx->cur_inode_last_extent, next_offset);
+	if (ret == 0)
+		ret = send_hole(sctx, next_offset);
+	else if (ret == 1)
+		ret = 0;
+
+	sctx->cur_inode_last_extent = next_offset;
+
 	return ret;
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v3] btrfs: send: fix missing hole punching during incremental send
  2026-10-23  8:51 ` [PATCH v3] " fdmanana
@ 2026-10-23  9:21   ` Qu Wenruo
  0 siblings, 0 replies; 6+ messages in thread
From: Qu Wenruo @ 2026-10-23  9:21 UTC (permalink / raw)
  To: fdmanana, linux-btrfs



在 2026/10/23 19:21, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
> 
> When using the no-holes feature (enabled by default since btrfs-progs
> v5.15) and doing an incremental send, we can miss issuing a hole punching
> command when the following conditions happen:
> 
> 1) A hole is punched in a tail section of a file extent item that happens
>     to be the last item in a leaf (for example the extent covers the file
>     range [1M, 3M) and a hole is punched in the range [2M, 3M)). Between
>     the end of this file extent item's range and the i_size, there are
>     other file extent items;
> 
> 2) The next leaf was not modified in the send snapshot, remaining shared
>     with the parent snapshot.
> 
> In this scenario we are unable to detect a hole was punched and never
> issue a hole punch command because with no-holes there we don't have an
> exlicit file extent item to mark a hole.

s/exlicit/explicit/

> 
> Fix this by teaching maybe_send_hole() to detect such case.
> A test case for fstests will follow soon.
> 
> Reported-by: Tristan Elvers <tristanelvers@gmail.com>
> Link: https://lore.kernel.org/linux-btrfs/CAFoLKK+aErV4p6ZJPnVeH5SHQ5++0f=8ZgxPWe-E-CkeKBs8PA@mail.gmail.com/
> Fixes: 16e7549f045d ("Btrfs: incompatible format change to remove hole extents")
> Signed-off-by: Filipe Manana <fdmanana@suse.com>

Reviewed-by: Qu Wenruo <wqu@suse.com>

Thanks,
Qu

> ---
> 
> V3: Avoid tree searches in case the i_size is in the last file extent
>      item and in case there is no hole between the last file extent
>      item and the first one in the next leaf.
> 
> V2: Address a couple issues pointed by sashiko, simplify a few things.
> 
>   fs/btrfs/send.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++---
>   1 file changed, 65 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
> index c523bf950c89..1b903e75cec0 100644
> --- a/fs/btrfs/send.c
> +++ b/fs/btrfs/send.c
> @@ -6371,6 +6371,9 @@ static int range_is_hole_in_parent(struct send_ctx *sctx,
>   static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
>   			   struct btrfs_key *key)
>   {
> +	BTRFS_PATH_AUTO_FREE(next_path);
> +	const u64 cur_extent_end = btrfs_file_extent_end(path);
> +	u64 next_offset;
>   	int ret = 0;
>   
>   	if (sctx->cur_ino != key->objectid || !need_send_hole(sctx))
> @@ -6397,14 +6400,72 @@ static int maybe_send_hole(struct send_ctx *sctx, struct btrfs_path *path,
>   		ret = range_is_hole_in_parent(sctx,
>   					      sctx->cur_inode_last_extent,
>   					      key->offset);
> +		if (ret == 0)
> +			ret = send_hole(sctx, key->offset);
> +		else if (ret == 1)
> +			ret = 0;
> +
>   		if (ret < 0)
>   			return ret;
> -		else if (ret == 0)
> -			ret = send_hole(sctx, key->offset);
> +	}
> +	sctx->cur_inode_last_extent = cur_extent_end;
> +
> +	/*
> +	 * If we are the last file extent item in a leaf, we may have had a tail
> +	 * section that got a hole punched or the extent replaced with a smaller
> +	 * one, and if there is no change in the next leaf, we must find out if
> +	 * we have such a hole and punch it.
> +	 */
> +	if (!btrfs_fs_incompat(sctx->send_root->fs_info, NO_HOLES) ||
> +	    (sctx->cur_inode_size <= cur_extent_end) ||
> +	    (path->slots[0] != btrfs_header_nritems(path->nodes[0]) - 1))
> +		return 0;
> +
> +	next_path = alloc_path_for_send();
> +	if (!next_path)
> +		return -ENOMEM;
> +
> +	ret = btrfs_search_slot(NULL, sctx->send_root, key, next_path, 0, 0);
> +	if (ret < 0)
> +		return ret;
> +	if (WARN_ON(ret != 0))
> +		return -EUCLEAN;
> +
> +	ret = btrfs_next_leaf(sctx->send_root, next_path);
> +	if (ret < 0) {
> +		return ret;
> +	} else if (ret > 0) {
> +		/*
> +		 * No next leaf, must punch hole to i_size and set the path back
> +		 * to where it was before.
> +		 */
> +		next_offset = sctx->cur_inode_size;
> +	} else {
> +		struct btrfs_key next_key;
> +
> +		btrfs_item_key_to_cpu(next_path->nodes[0], &next_key,
> +				      next_path->slots[0]);
> +		if (next_key.objectid != key->objectid ||
> +		    next_key.type != BTRFS_EXTENT_DATA_KEY)
> +			next_offset = sctx->cur_inode_size;
>   		else
> -			ret = 0;
> +			next_offset = next_key.offset;
>   	}
> -	sctx->cur_inode_last_extent = btrfs_file_extent_end(path);
> +
> +	if (next_offset == cur_extent_end)
> +		return 0;
> +
> +	btrfs_free_path(next_path);
> +	next_path = NULL;
> +
> +	ret = range_is_hole_in_parent(sctx, sctx->cur_inode_last_extent, next_offset);
> +	if (ret == 0)
> +		ret = send_hole(sctx, next_offset);
> +	else if (ret == 1)
> +		ret = 0;
> +
> +	sctx->cur_inode_last_extent = next_offset;
> +
>   	return ret;
>   }
>   


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-23  9:22 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-21 20:17 [PATCH] btrfs: send: fix missing hole punching during incremental send fdmanana
2026-10-21 21:41 ` [PATCH v2] " fdmanana
2026-10-23  6:39   ` Qu Wenruo
2026-10-23  8:51     ` Filipe Manana
2026-10-23  8:51 ` [PATCH v3] " fdmanana
2026-10-23  9:21   ` Qu Wenruo

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.