From: Zorro Lang <zlang@kernel.org>
To: Matthias Goergens <matthias.goergens@gmail.com>
Cc: fstests@vger.kernel.org
Subject: Re: [PATCH 2/2] generic: test FIDEDUPERANGE result reporting
Date: Thu, 13 Aug 2026 03:39:37 +0800 [thread overview]
Message-ID: <any8FDY-v2TmIxcW@zlang-mailbox> (raw)
In-Reply-To: <20260805071839.3423779-3-matthias.goergens@gmail.com>
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 <matthias.goergens@gmail.com>
> ---
> 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
>
prev parent reply other threads:[~2026-08-12 19:39 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-05 7:18 [PATCH 0/2] fstests: expect and test truthful FIDEDUPERANGE progress Matthias Goergens
2026-08-05 7:18 ` [PATCH 1/2] generic/517: expect truthful dedupe progress Matthias Goergens
2026-08-11 8:36 ` Zorro Lang
2026-08-05 7:18 ` [PATCH 2/2] generic: test FIDEDUPERANGE result reporting Matthias Goergens
2026-08-12 19:39 ` Zorro Lang [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=any8FDY-v2TmIxcW@zlang-mailbox \
--to=zlang@kernel.org \
--cc=fstests@vger.kernel.org \
--cc=matthias.goergens@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox