fs/fuse/file.c | 4 ++++ 1 file changed, 4 insertions(+)
__fuse_copy_file_range() may receive a successful copy reply with zero
bytes copied. This can happen even though the VFS only calls the file
operation with a non-zero length, for example if the FUSE daemon reaches
EOF while the kernel has stale source size information.
In that case no data was written to the destination. Do not invalidate
the destination page cache or update the destination timestamps and cached
size state for a zero-byte copy result.
Fixes: 88bc7d5097a1 ("fuse: add support for copy_file_range()")
Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
---
fs/fuse/file.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index ceada75310b8..75eb97b26912 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -3040,6 +3040,10 @@ static ssize_t __fuse_copy_file_range(struct file *file_in, loff_t pos_in,
err = -EIO;
goto out;
}
+ if (!bytes_copied) {
+ err = 0;
+ goto out;
+ }
truncate_inode_pages_range(inode_out->i_mapping,
ALIGN_DOWN(pos_out, PAGE_SIZE),
--
2.51.0
On Thu, 2026-07-23 at 18:07 +0800, Yichong Chen wrote:
> __fuse_copy_file_range() may receive a successful copy reply with
> zero
> bytes copied. This can happen even though the VFS only calls the
> file
> operation with a non-zero length, for example if the FUSE daemon
> reaches
> EOF while the kernel has stale source size information.
>
> In that case no data was written to the destination. Do not
> invalidate
> the destination page cache or update the destination timestamps and
> cached
> size state for a zero-byte copy result.
>
> Fixes: 88bc7d5097a1 ("fuse: add support for copy_file_range()")
> Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
> ---
> fs/fuse/file.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index ceada75310b8..75eb97b26912 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c
> @@ -3040,6 +3040,10 @@ static ssize_t __fuse_copy_file_range(struct
> file *file_in, loff_t pos_in,
> err = -EIO;
> goto out;
> }
> + if (!bytes_copied) {
> + err = 0;
> + goto out;
> + }
>
> truncate_inode_pages_range(inode_out->i_mapping,
> ALIGN_DOWN(pos_out, PAGE_SIZE),
Thanks, this looks good to me.
My style preference would be to make it an "else if" because bytes_copied is
used in the if-statement above already too. I don't think it is required to
adjust this, the functioning will remain the same.
Acked-by: Niels de Vos <ndevos@ibm.com>
On Thu, Jul 23, 2026 at 12:15 PM Yichong Chen <chenyichong@uniontech.com> wrote:
>
> __fuse_copy_file_range() may receive a successful copy reply with zero
> bytes copied. This can happen even though the VFS only calls the file
> operation with a non-zero length, for example if the FUSE daemon reaches
> EOF while the kernel has stale source size information.
>
> In that case no data was written to the destination. Do not invalidate
> the destination page cache or update the destination timestamps and cached
> size state for a zero-byte copy result.
Why not?
If the FUSE daemon reaches EOF and VFS thinks that there is still something
to read then obviously the cached size is incorrect.
Also invalidating the last page cache in this case does not seem like a terrible
idea in any case.
Are you proposing this "fix" because an application encountered a problem
or unexpected behavior or why?
Thanks,
Amir.
>
> Fixes: 88bc7d5097a1 ("fuse: add support for copy_file_range()")
> Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
> ---
> fs/fuse/file.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index ceada75310b8..75eb97b26912 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c
> @@ -3040,6 +3040,10 @@ static ssize_t __fuse_copy_file_range(struct file *file_in, loff_t pos_in,
> err = -EIO;
> goto out;
> }
> + if (!bytes_copied) {
> + err = 0;
> + goto out;
> + }
>
> truncate_inode_pages_range(inode_out->i_mapping,
> ALIGN_DOWN(pos_out, PAGE_SIZE),
> --
> 2.51.0
>
>
On Thu, Jul 23, 2026 at 12:43:29PM +0200, Amir Goldstein wrote: > On Thu, Jul 23, 2026 at 12:15 PM Yichong Chen <chenyichong@uniontech.com> wrote: > > > > __fuse_copy_file_range() may receive a successful copy reply with zero > > bytes copied. This can happen even though the VFS only calls the file > > operation with a non-zero length, for example if the FUSE daemon reaches > > EOF while the kernel has stale source size information. > > > > In that case no data was written to the destination. Do not invalidate > > the destination page cache or update the destination timestamps and cached > > size state for a zero-byte copy result. > > Why not? > If the FUSE daemon reaches EOF and VFS thinks that there is still something > to read then obviously the cached size is incorrect. > Also invalidating the last page cache in this case does not seem like a terrible > idea in any case. > > Are you proposing this "fix" because an application encountered a problem > or unexpected behavior or why? I agree with Amir, but I think it is worse. There is no check in that function for len not to be zero. So if something calls this with len set to zero, we expect it to either do it and call all the updates. Calling the write and react on the result without knowing if this was intended doesn't look right to me. Someone might actually call this with len zero. > > Thanks, > Amir. > Thanks, Horst
Hi Amir, Thanks for taking a look. My concern is about the destination-side effects when the daemon returns a zero-byte success. In that case copy_file_range() returns 0 and no data has been copied to the destination, so treating the destination as modified looks wrong to me. One visible effect is the timestamp update. With writeback_cache enabled, I can reproduce a non-zero-length FUSE_COPY_FILE_RANGE request for which the daemon returns 0, and the kernel still sends utimens() for the destination. This can make userspace tools such as sync, backup or build tools observe the destination as modified even though no data was copied. In many cases this may only cause extra work, but it is still an observable metadata change for a zero-byte result. Another effect is the destination page-cache invalidation. For a zero-byte copy there is no copied range to invalidate, but the current range calculation can still invalidate destination cache unnecessarily. For example, when pos_out is 0, the calculated range becomes truncate_inode_pages_range(mapping, 0, -1), so it may invalidate the destination mapping from the beginning. This can cause extra FUSE read I/O later. I agree that the stale source size is a separate issue, but the current destination updates do not fix that. They operate on inode_out, so they do not make inode_in's cached size consistent with the lower source file. That said, my current patch is not complete because file_modified(file_out) is called before the daemon reply is known and can already update the destination timestamps. I am doing a bit more testing around the zero-byte case. If you think this behavior is worth fixing, I can send a v2 with the timestamp/cache update part corrected. Thanks, Yichong
© 2016 - 2026 Red Hat, Inc.