Linux: test sizes larger than UINT_MAX for copy_file_range

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>
Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
Tested-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
This commit is contained in:
Xi Ruoyao 2026-01-08 15:27:55 +08:00 committed by Andreas K. Hüttel
parent ee77bb99b7
commit bcf231ec71
No known key found for this signature in database
GPG Key ID: DC2B16215ED5412A
2 changed files with 50 additions and 15 deletions

View File

@ -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_SETATTR struct fuse_setattr_in
#define support_fuse_payload_type_WRITE struct fuse_write_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 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) \ #define support_fuse_cast(typ, inh) \
((support_fuse_payload_type_##typ *) \ ((support_fuse_payload_type_##typ *) \
support_fuse_cast_internal ((inh), FUSE_##typ)) support_fuse_cast_internal ((inh), FUSE_##typ))

View File

@ -25,6 +25,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <stdatomic.h>
#include <string.h> #include <string.h>
#include <support/check.h> #include <support/check.h>
#include <support/fuse.h> #include <support/fuse.h>
@ -35,16 +36,32 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.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 static void
fuse_thread (struct support_fuse *f, void *closure) 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 /* A large size, so that the kernel does not fail the
copy_file_range attempt before performing the FUSE callback. copy_file_range attempt before performing the FUSE callback.
Only the source file size matters to the kernel, but both files Only the source file size matters to the kernel, but both files
use the same size for simplicity. */ use the same size for simplicity. */
const uint64_t file_size = 1LLU << 61;
struct fuse_in_header *inh; struct fuse_in_header *inh;
while ((inh = support_fuse_next (f)) != NULL) 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 struct fuse_copy_file_range_in *p
= support_fuse_cast (COPY_FILE_RANGE, inh); = support_fuse_cast (COPY_FILE_RANGE, inh);
/* Verify this is a copy from source to dest, starting at verify_fuse_request (p);
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);
/* Pretend the copy succeeded. */ /* Pretend the copy succeeded. */
struct fuse_write_out out = { .size = p->len }; struct fuse_write_out out = { .size = p->len };
@ -123,6 +133,23 @@ fuse_thread (struct support_fuse *f, void *closure)
} }
break; 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: case FUSE_FLUSH:
support_fuse_reply_empty (f); support_fuse_reply_empty (f);
break; break;
@ -170,10 +197,14 @@ test_size (struct support_fuse *f, off64_t size)
FAIL_UNSUPPORTED ("copy_file_range not supported"); 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 /* 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: 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 We must AND the expression with SSIZE_MAX for 32-bit platforms where
SSIZE_MAX is less than UINT_MAX. SSIZE_MAX is less than UINT_MAX.
*/ */
@ -201,8 +232,11 @@ test_all_sizes (struct support_fuse *f)
test_size (f, UINT_MAX + i); test_size (f, UINT_MAX + i);
/* We would like to test larger values than UINT_MAX here, but they /* 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 do not work if the FUSE protocol still uses uint32_t for the
copy_file_range result in struct fuse_write_out. */ 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 * static void *