Re: [PATCH 2/2] generic: test FIDEDUPERANGE result reporting
Zorro Lang <[email protected]>
| Newsgroups | org.kernel.vger.fstests |
|---|---|
| Message-ID | <any8FDY-v2TmIxcW@zlang-mailbox> |
On Wed, Aug 05, 2026 at 03:18:39PM +0800, Matthias Goergens wrote: > xfs_io retries shortened dedupe operations, so its output cannot verify one > raw multi-destination ioctl or distinguish an explicit zero-length request > from a nonzero request shortened to zero. > > Add a small FIDEDUPERANGE helper that reports each destination's raw > bytes_deduped and status fields. Use it in generic/806 to check an explicit > zero-length success, a request that is shortened for an interior destination > but remains full-length at EOF, and a sub-block request that returns -EINVAL > for the interior destination while succeeding at EOF. > > Compute the request lengths from the filesystem block size so the expected > semantics do not assume 4K blocks. The paired kernel series is linked from > the cover letter. > > The test passes on btrfs and XFS with the paired kernel changes. > > Signed-off-by: Matthias Goergens <[email protected]> > --- > src/Makefile | 2 +- > src/fideduperange.c | 138 ++++++++++++++++++++++++++++++++++++++++++ Need to update .gitignore file too. > tests/generic/806 | 83 +++++++++++++++++++++++++ > tests/generic/806.out | 4 ++ > 4 files changed, 226 insertions(+), 1 deletion(-) > create mode 100644 src/fideduperange.c > create mode 100755 tests/generic/806 > create mode 100644 tests/generic/806.out > > diff --git a/src/Makefile b/src/Makefile > index 76cf50c3..e686fe27 100644 > --- a/src/Makefile > +++ b/src/Makefile > @@ -25,7 +25,7 @@ TARGETS = dirstress fill fill2 getpagesize holes lstat64 \ > > LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \ > preallo_rw_pattern_writer ftrunc trunc fs_perms testx looptest \ > - locktest unwritten_mmap bulkstat_unlink_test deduperace \ > + locktest unwritten_mmap bulkstat_unlink_test deduperace fideduperange \ > bulkstat_unlink_test_modified t_dir_offset t_futimens t_immutable \ > stale_handle pwrite_mmap_blocked t_dir_offset2 seek_sanity_test \ > seek_copy_test t_readdir_1 t_readdir_2 fsync-tester nsexec cloner \ > diff --git a/src/fideduperange.c b/src/fideduperange.c > new file mode 100644 > index 00000000..24813de8 > --- /dev/null > +++ b/src/fideduperange.c > @@ -0,0 +1,138 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Issue a raw FIDEDUPERANGE ioctl and report each destination's result. > + */ > +#include <errno.h> > +#include <fcntl.h> > +#include <inttypes.h> > +#include <linux/fs.h> > +#include <stdint.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <sys/ioctl.h> > +#include <unistd.h> > + > +#ifndef FIDEDUPERANGE > +/* These definitions must match the kernel UAPI. */ > +#define FILE_DEDUPE_RANGE_SAME 0 > +#define FILE_DEDUPE_RANGE_DIFFERS 1 > + > +struct file_dedupe_range_info { > + __s64 dest_fd; > + __u64 dest_offset; > + __u64 bytes_deduped; > + __s32 status; > + __u32 reserved; > +}; > + > +struct file_dedupe_range { > + __u64 src_offset; > + __u64 src_length; > + __u16 dest_count; > + __u16 reserved1; > + __u32 reserved2; > + struct file_dedupe_range_info info[0]; > +}; > + > +#define FIDEDUPERANGE _IOWR(0x94, 54, struct file_dedupe_range) > +#endif > + > +static void > +usage(const char *progname) > +{ > + fprintf(stderr, > + "Usage: %s src src_offset length dest dest_offset " > + "[dest dest_offset ...]\n", > + progname); > + exit(EXIT_FAILURE); > +} > + > +static uint64_t > +parse_u64(const char *arg, const char *name) > +{ > + char *end; > + unsigned long long value; > + > + errno = 0; > + value = strtoull(arg, &end, 0); > + if (errno || end == arg || *end != '\0') { > + fprintf(stderr, "Invalid %s: %s\n", name, arg); > + exit(EXIT_FAILURE); > + } > + > + return value; > +} > + > +int > +main(int argc, char **argv) > +{ > + struct file_dedupe_range *range; > + size_t range_size; > + unsigned int dest_count; > + unsigned int i; > + int src_fd; > + int ret; > + > + if (argc < 6 || (argc - 4) % 2 != 0) > + usage(argv[0]); > + > + dest_count = (argc - 4) / 2; > + if (dest_count > UINT16_MAX) { > + fprintf(stderr, "Too many destinations: %u\n", dest_count); > + return EXIT_FAILURE; > + } > + > + range_size = sizeof(*range) + > + dest_count * sizeof(struct file_dedupe_range_info); > + range = calloc(1, range_size); > + if (!range) { > + perror("calloc"); > + return EXIT_FAILURE; > + } > + > + src_fd = open(argv[1], O_RDONLY); > + if (src_fd < 0) { > + perror(argv[1]); > + free(range); > + return EXIT_FAILURE; > + } > + > + range->src_offset = parse_u64(argv[2], "source offset"); > + range->src_length = parse_u64(argv[3], "length"); > + range->dest_count = dest_count; > + > + for (i = 0; i < dest_count; i++) { > + const char *dest_path = argv[4 + i * 2]; > + > + range->info[i].dest_fd = open(dest_path, O_RDWR); > + if (range->info[i].dest_fd < 0) { > + perror(dest_path); > + ret = EXIT_FAILURE; > + goto close_destinations; > + } > + range->info[i].dest_offset = > + parse_u64(argv[5 + i * 2], "destination offset"); > + } > + > + if (ioctl(src_fd, FIDEDUPERANGE, range) < 0) { > + perror("FIDEDUPERANGE"); > + ret = EXIT_FAILURE; > + goto close_destinations; > + } > + > + for (i = 0; i < dest_count; i++) > + printf("destination %u: bytes=%" PRIu64 " status=%d\n", i, > + (uint64_t)range->info[i].bytes_deduped, > + range->info[i].status); > + ret = EXIT_SUCCESS; > + > +close_destinations: > + while (i > 0) { > + i--; > + close(range->info[i].dest_fd); > + } > + close(src_fd); > + free(range); > + return ret; > +} > diff --git a/tests/generic/806 b/tests/generic/806 > new file mode 100755 > index 00000000..98254b79 > --- /dev/null > +++ b/tests/generic/806 > @@ -0,0 +1,83 @@ > +#! /bin/bash > +# SPDX-License-Identifier: GPL-2.0 > +# Copyright (c) 2026 Matthias Goergens. All Rights Reserved. > +# > +# FS QA Test No. 806 > +# > +# Check FIDEDUPERANGE result reporting for zero-length requests and for a > +# multi-destination request where the VFS shortens only some destinations. > +# > +. ./common/preamble > +_begin_fstest auto quick dedupe clone > + > +. ./common/reflink > + > +_require_scratch_dedupe > +_require_test_program "fideduperange" > + > +_scratch_mkfs >>$seqres.full 2>&1 > +_scratch_mount > + > +block_size=$(_get_block_size "$SCRATCH_MNT") Better to use "_get_file_block_size". This new test looks good, but let's wait until kernel patches get acked first. > +short_length=$((2 * block_size)) > +full_length=$((short_length + 100)) > + > +src=$SCRATCH_MNT/src > +src_short=$SCRATCH_MNT/src-short > +dest_interior=$SCRATCH_MNT/dest-interior > +dest_eof=$SCRATCH_MNT/dest-eof > +dest_short_eof=$SCRATCH_MNT/dest-short-eof > + > +$XFS_IO_PROG -f -c "pwrite -S 0x6b 0 $full_length" "$src" \ > + >>$seqres.full 2>&1 > +$XFS_IO_PROG -f -c "pwrite -S 0x6b 0 100" "$src_short" \ > + >>$seqres.full 2>&1 > +$XFS_IO_PROG -f -c "pwrite -S 0x6b 0 $((full_length + block_size))" \ > + "$dest_interior" >>$seqres.full 2>&1 > +$XFS_IO_PROG -f -c "pwrite -S 0x6b 0 $full_length" "$dest_eof" \ > + >>$seqres.full 2>&1 > +$XFS_IO_PROG -f -c "pwrite -S 0x6b 0 100" "$dest_short_eof" \ > + >>$seqres.full 2>&1 > + > +check_results() > +{ > + local description="$1" > + local expected="$2" > + shift 2 > + local result > + local ret > + > + result=$($here/src/fideduperange "$@" 2>&1) > + ret=$? > + { > + echo "$description" > + echo "$result" > + } >>$seqres.full > + > + if [ $ret -ne 0 ]; then > + _fail "$description: ioctl helper failed" > + fi > + if [ "$result" != "$expected" ]; then > + _fail "$description: unexpected ioctl result" > + fi > + > + echo "$description" > +} > + > +expected="destination 0: bytes=0 status=0" > +check_results "zero length: 0 bytes, SAME" "$expected" \ > + "$src" 0 0 "$dest_interior" 0 > + > +expected="destination 0: bytes=$short_length status=0 > +destination 1: bytes=$full_length status=0" > +check_results "mixed partial request: shortened, full" "$expected" \ > + "$src" 0 "$full_length" "$dest_interior" 0 "$dest_eof" 0 > + > +expected="destination 0: bytes=0 status=-22 > +destination 1: bytes=100 status=0" > +check_results "mixed sub-block request: EINVAL, full" "$expected" \ > + "$src_short" 0 100 "$dest_interior" 0 "$dest_short_eof" 0 > + > +# success, all done > +status=0 > +exit > diff --git a/tests/generic/806.out b/tests/generic/806.out > new file mode 100644 > index 00000000..d7226e34 > --- /dev/null > +++ b/tests/generic/806.out > @@ -0,0 +1,4 @@ > +QA output created by 806 > +zero length: 0 bytes, SAME > +mixed partial request: shortened, full > +mixed sub-block request: EINVAL, full > -- > 2.55.0 >