[PATCH 3/3] Linux: test sizes larger than UINT_MAX for copy_file_range

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Thu Jan 8 16:04:33 GMT 2026



On 08/01/26 04:27, Xi Ruoyao wrote:
> If the kernel supports the COPY_FILE_RANGE_64 FUSE interface, we can
> safely tests the large size values.
> 
> Signed-off-by: Xi Ruoyao <xry111@xry111.site>

LGTM, and it fixes the tst-copy_file_range-large on recent kernel.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
Tested-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  support/fuse.h                                |  1 +
>  .../sysv/linux/tst-copy_file_range-large.c    | 64 ++++++++++++++-----
>  2 files changed, 50 insertions(+), 15 deletions(-)
> 
> diff --git a/support/fuse.h b/support/fuse.h
> index 581c83b08d..9e35105a57 100644
> --- a/support/fuse.h
> +++ b/support/fuse.h
> @@ -97,6 +97,7 @@ void *support_fuse_cast_name_internal (struct fuse_in_header *, uint32_t,
>  #define support_fuse_payload_type_SETATTR struct fuse_setattr_in
>  #define support_fuse_payload_type_WRITE struct fuse_write_in
>  #define support_fuse_payload_type_COPY_FILE_RANGE struct fuse_copy_file_range_in
> +#define support_fuse_payload_type_COPY_FILE_RANGE_64 struct fuse_copy_file_range_in
>  #define support_fuse_cast(typ, inh)                     \
>    ((support_fuse_payload_type_##typ *)                  \
>     support_fuse_cast_internal ((inh), FUSE_##typ))
> diff --git a/sysdeps/unix/sysv/linux/tst-copy_file_range-large.c b/sysdeps/unix/sysv/linux/tst-copy_file_range-large.c
> index e92f7d4557..4a323df539 100644
> --- a/sysdeps/unix/sysv/linux/tst-copy_file_range-large.c
> +++ b/sysdeps/unix/sysv/linux/tst-copy_file_range-large.c
> @@ -25,6 +25,7 @@
>  #include <fcntl.h>
>  #include <limits.h>
>  #include <stdio.h>
> +#include <stdatomic.h>
>  #include <string.h>
>  #include <support/check.h>
>  #include <support/fuse.h>
> @@ -35,16 +36,32 @@
>  #include <sys/stat.h>
>  #include <unistd.h>
>  
> +static _Atomic bool fuse_has_copy_file_range_64 = false;
> +static const uint64_t file_size = 1LLU << 61;
> +
> +/* Node IDs for our test files.  */
> +enum { NODE_SOURCE = 2, NODE_DEST = 3 };
> +
> +/* Verify this is a copy from source to dest, starting at
> +   offset 0.  */
> +static void
> +verify_fuse_request (struct fuse_copy_file_range_in *p)
> +{
> +  TEST_COMPARE (p->fh_in, NODE_SOURCE);
> +  TEST_COMPARE (p->nodeid_out, NODE_DEST);
> +  TEST_COMPARE (p->off_in, 0);
> +  TEST_COMPARE (p->off_out, 0);
> +  TEST_VERIFY (p->len > 0);
> +  TEST_VERIFY (p->len <= file_size);
> +}
> +
>  static void
>  fuse_thread (struct support_fuse *f, void *closure)
>  {
> -  /* Node IDs for our test files.  */
> -  enum { NODE_SOURCE = 2, NODE_DEST = 3 };
>    /* A large size, so that the kernel does not fail the
>       copy_file_range attempt before performing the FUSE callback.
>       Only the source file size matters to the kernel, but both files
>       use the same size for simplicity.  */
> -  const uint64_t file_size = 1LLU << 61;
>  
>    struct fuse_in_header *inh;
>    while ((inh = support_fuse_next (f)) != NULL)
> @@ -108,14 +125,7 @@ fuse_thread (struct support_fuse *f, void *closure)
>              struct fuse_copy_file_range_in *p
>                = support_fuse_cast (COPY_FILE_RANGE, inh);
>  
> -            /* Verify this is a copy from source to dest, starting at
> -               offset 0.  */
> -            TEST_COMPARE (p->fh_in, NODE_SOURCE);
> -            TEST_COMPARE (p->nodeid_out, NODE_DEST);
> -            TEST_COMPARE (p->off_in, 0);
> -            TEST_COMPARE (p->off_out, 0);
> -            TEST_VERIFY (p->len > 0);
> -            TEST_VERIFY (p->len <= file_size);
> +            verify_fuse_request (p);
>  
>              /* Pretend the copy succeeded.  */
>              struct fuse_write_out out = { .size = p->len };
> @@ -123,6 +133,23 @@ fuse_thread (struct support_fuse *f, void *closure)
>            }
>            break;
>  
> +        case FUSE_COPY_FILE_RANGE_64:
> +          {
> +            atomic_store (&fuse_has_copy_file_range_64, true);
> +
> +            struct fuse_copy_file_range_in *p
> +              = support_fuse_cast (COPY_FILE_RANGE_64, inh);
> +
> +            verify_fuse_request (p);
> +
> +            /* Pretend the copy succeeded.  */
> +            struct fuse_copy_file_range_out out = {
> +              .bytes_copied = p->len,
> +            };
> +            support_fuse_reply (f, &out, sizeof (out));
> +          }
> +          break;
> +
>          case FUSE_FLUSH:
>            support_fuse_reply_empty (f);
>            break;
> @@ -170,10 +197,14 @@ test_size (struct support_fuse *f, off64_t size)
>        FAIL_UNSUPPORTED ("copy_file_range not supported");
>      }
>  
> +  if (atomic_load (&fuse_has_copy_file_range_64))
> +    TEST_COMPARE (copied, size);
> +
>    /* To avoid the negative return value in Linux versions 6.18 the size is
> -     silently clamped to UINT_MAX & PAGE_MASK.  Accept that return value
> +     silently clamped to UINT_MAX & PAGE_MASK and the change has been
> +     backported to stable kernel release series.  Accept that return value
>       too.  See:
> -     <https://github.com/torvalds/linux/commit/1e08938c3694f707bb165535df352ac97a8c75c9>.
> +     <https://git.kernel.org/torvalds/c/1e08938c3694>.
>       We must AND the expression with SSIZE_MAX for 32-bit platforms where
>       SSIZE_MAX is less than UINT_MAX.
>    */
> @@ -201,8 +232,11 @@ test_all_sizes (struct support_fuse *f)
>      test_size (f, UINT_MAX + i);
>  
>    /* We would like to test larger values than UINT_MAX here, but they
> -     do not work because the FUSE protocol uses uint32_t for the
> -     copy_file_range result in struct fuse_write_out.  */
> +     do not work if the FUSE protocol still uses uint32_t for the
> +     copy_file_range result in struct fuse_write_out (on Linux < 6.18).  */
> +  if (atomic_load (&fuse_has_copy_file_range_64))
> +    for (int i = -10; i <= 0; ++i)
> +      test_size (f, file_size + i);
>  }
>  
>  static void *



More information about the Libc-alpha mailing list