Linux XFS filesystem development
 help / color / mirror / Atom feed
  • * [PATCH v2] generic: test FIDEDUPERANGE result reporting with REPORT_PROGRESS
           [not found] <20260805071839.3423779-1-matthias.goergens@gmail.com>
           [not found] ` <20260805071839.3423779-2-matthias.goergens@gmail.com>
    @ 2026-08-17  9:50 ` Matthias Goergens
      1 sibling, 0 replies; 2+ messages in thread
    From: Matthias Goergens @ 2026-08-17  9:50 UTC (permalink / raw)
      To: fstests; +Cc: zlang, fdmanana, linux-xfs, linux-btrfs
    
    Add a raw FIDEDUPERANGE helper that reports each destination's
    bytes_deduped and status, and a test for the proposed
    FILE_DEDUPE_RANGE_REPORT_PROGRESS flag: explicit zero-length success,
    a request the VFS shortens for an interior destination, a sub-block
    request that makes no progress (zero, no error), and differing
    contents (the examined span).  One unflagged case pins the legacy
    reporting so the default ABI cannot change silently.
    
    The kernel side is under review:
    https://lore.kernel.org/linux-fsdevel/20260805071414.3414870-1-matthias.goergens@gmail.com/
    The commit id in _wants_kernel_commit is a placeholder until it lands.
    
    Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com>
    ---
    v2 (per Zorro's review and the kernel thread):
    - drop the generic/517 golden-output change: the kernel side is now an
      opt-in flag, so default reporting is unchanged
    - test the flag semantics proposed by Darrick in the kernel thread,
      including bytes_deduped as an advance hint on DIFFERS and zero on a
      per-destination error
    - sentinel-fill the helper's output fields so the test proves the
      kernel wrote them
    - add the helper binary to .gitignore
    - use _wants_kernel_commit (placeholder id until the kernel side lands)
    
    Every expected line was produced by a kernel with the flag patch
    applied (btrfs scratch under QEMU) rather than written from the spec.
     .gitignore            |   1 +
     src/Makefile          |   2 +-
     src/fideduperange.c   | 166 ++++++++++++++++++++++++++++++++++++++++++
     tests/generic/806     | 118 ++++++++++++++++++++++++++++++
     tests/generic/806.out |   7 ++
     5 files changed, 293 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/.gitignore b/.gitignore
    index 0b6b9452..3d6ce89d 100644
    --- a/.gitignore
    +++ b/.gitignore
    @@ -71,6 +71,7 @@ tags
     /src/cloner
     /src/dbtest
     /src/deduperace
    +/src/fideduperange
     /src/detached_mounts_propagation
     /src/devzero
     /src/dio-append-buf-fault
    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..b36365f6
    --- /dev/null
    +++ b/src/fideduperange.c
    @@ -0,0 +1,166 @@
    +// 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 <getopt.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>
    +
    +/* Local copies of the UAPI types: the system linux/fs.h may predate the
    + * FILE_DEDUPE_RANGE_REPORT_PROGRESS flag.  The layout matches the kernel
    + * UAPI. */
    +#ifndef FILE_DEDUPE_RANGE_REPORT_PROGRESS
    +#define FILE_DEDUPE_RANGE_REPORT_PROGRESS (1U << 0)
    +#endif
    +#define FILE_DEDUPE_RANGE_SAME		0
    +#define FILE_DEDUPE_RANGE_DIFFERS	1
    +
    +struct file_dedupe_range_info_local {
    +	__s64 dest_fd;
    +	__u64 dest_offset;
    +	__u64 bytes_deduped;
    +	__s32 status;
    +	__u32 reserved;
    +};
    +
    +struct file_dedupe_range_local {
    +	__u64 src_offset;
    +	__u64 src_length;
    +	__u16 dest_count;
    +	__u16 reserved1;
    +	__u32 flags;
    +	struct file_dedupe_range_info_local info[0];
    +};
    +
    +#ifndef FIDEDUPERANGE
    +#define FIDEDUPERANGE _IOWR(0x94, 54, struct file_dedupe_range_local)
    +#endif
    +
    +static __u32 range_flags;
    +
    +static void
    +usage(const char *progname)
    +{
    +	fprintf(stderr,
    +		"Usage: %s [-f] src src_offset length dest dest_offset "
    +		"[dest dest_offset ...]\n"
    +		"  -f: set FILE_DEDUPE_RANGE_REPORT_PROGRESS\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_local *range;
    +	size_t range_size;
    +	unsigned int dest_count;
    +	unsigned int i;
    +	int src_fd;
    +	int ret;
    +	int c;
    +
    +	const char *progname = argv[0];
    +
    +	while ((c = getopt(argc, argv, "f")) != -1) {
    +		switch (c) {
    +		case 'f':
    +			range_flags |= FILE_DEDUPE_RANGE_REPORT_PROGRESS;
    +			break;
    +		default:
    +			usage(progname);
    +		}
    +	}
    +	argc -= optind;
    +	argv += optind;
    +
    +	if (argc < 5 || (argc - 3) % 2 != 0)
    +		usage(progname);
    +
    +	dest_count = (argc - 3) / 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_local);
    +	range = calloc(1, range_size);
    +	if (!range) {
    +		perror("calloc");
    +		return EXIT_FAILURE;
    +	}
    +
    +	src_fd = open(argv[0], O_RDONLY);
    +	if (src_fd < 0) {
    +		perror(argv[0]);
    +		free(range);
    +		return EXIT_FAILURE;
    +	}
    +
    +	range->src_offset = parse_u64(argv[1], "source offset");
    +	range->src_length = parse_u64(argv[2], "length");
    +	range->dest_count = dest_count;
    +	range->flags = range_flags;
    +
    +	for (i = 0; i < dest_count; i++) {
    +		const char *dest_path = argv[3 + 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[4 + i * 2], "destination offset");
    +		/* Sentinels: the kernel must overwrite both output fields. */
    +		range->info[i].bytes_deduped = ~0ULL;
    +		range->info[i].status = INT32_MIN;
    +	}
    +
    +	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..70a5fe36
    --- /dev/null
    +++ b/tests/generic/806
    @@ -0,0 +1,118 @@
    +#! /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 with FILE_DEDUPE_RANGE_REPORT_PROGRESS:
    +# bytes_deduped as an advance hint for zero-length, shortened, zero-progress
    +# and differing requests, plus one unflagged case pinning the legacy
    +# reporting.
    +#
    +. ./common/preamble
    +_begin_fstest auto quick dedupe clone
    +
    +. ./common/reflink
    +
    +_require_scratch_dedupe
    +_require_test_program "fideduperange"
    +
    +# Placeholder commit id: the kernel patch is still under review; to be
    +# replaced with the real id when it lands (the helper only prints a hint).
    +_wants_kernel_commit XXXXXXXXXXXX \
    +	"vfs: add FILE_DEDUPE_RANGE_REPORT_PROGRESS flag to FIDEDUPERANGE"
    +
    +_scratch_mkfs >>$seqres.full 2>&1
    +_scratch_mount
    +
    +block_size=$(_get_block_size "$SCRATCH_MNT")
    +short_length=$((2 * block_size))
    +full_length=$((short_length + 100))
    +
    +src=$SCRATCH_MNT/src
    +src_short=$SCRATCH_MNT/src-short
    +src_diff=$SCRATCH_MNT/src-diff
    +dest_interior=$SCRATCH_MNT/dest-interior
    +dest_eof=$SCRATCH_MNT/dest-eof
    +dest_short_eof=$SCRATCH_MNT/dest-short-eof
    +dest_diff=$SCRATCH_MNT/dest-diff
    +
    +$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
    +$XFS_IO_PROG -f -c "pwrite -S 0x61 0 $full_length" "$src_diff" \
    +	>>$seqres.full 2>&1
    +$XFS_IO_PROG -f -c "pwrite -S 0x62 0 $full_length" "$dest_diff" \
    +	>>$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"
    +}
    +
    +# With the flag, bytes_deduped is an advance hint: actual bytes on SAME,
    +# the examined span on DIFFERS, zero when no further work is possible.
    +
    +expected="destination 0: bytes=0 status=0"
    +check_results "zero length: 0 bytes, SAME" "$expected" \
    +	-f "$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" \
    +	-f "$src" 0 "$full_length" "$dest_interior" 0 "$dest_eof" 0
    +
    +expected="destination 0: bytes=0 status=0
    +destination 1: bytes=100 status=0"
    +check_results "sub-block request: zero progress, full" "$expected" \
    +	-f "$src_short" 0 100 "$dest_interior" 0 "$dest_short_eof" 0
    +
    +expected="destination 0: bytes=$block_size status=1"
    +check_results "differing contents: DIFFERS with advance hint" "$expected" \
    +	-f "$src_diff" 0 "$full_length" "$dest_diff" 0
    +
    +# On a per-destination error, status carries the errno and bytes_deduped
    +# is zero (the huge offset fails VFS validation with EINVAL).
    +
    +expected="destination 0: bytes=0 status=-22"
    +check_results "error: bytes zero on EINVAL" "$expected" \
    +	-f "$src" 0 "$full_length" "$dest_interior" 9223372036854775807
    +
    +# Without the flag, the legacy reporting is unchanged: the requested
    +# length is reported even when the VFS shortens the range.
    +
    +expected="destination 0: bytes=$full_length status=0
    +destination 1: bytes=$full_length status=0"
    +check_results "unflagged legacy reporting: full length" "$expected" \
    +	"$src" 0 "$full_length" "$dest_interior" 0 "$dest_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..5c436cb3
    --- /dev/null
    +++ b/tests/generic/806.out
    @@ -0,0 +1,7 @@
    +QA output created by 806
    +zero length: 0 bytes, SAME
    +mixed partial request: shortened, full
    +sub-block request: zero progress, full
    +differing contents: DIFFERS with advance hint
    +error: bytes zero on EINVAL
    +unflagged legacy reporting: full length
    -- 
    2.55.0
    
    
    ^ permalink raw reply related	[flat|nested] 2+ messages in thread

  • end of thread, other threads:[~2026-08-17  9:50 UTC | newest]
    
    Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
    -- links below jump to the message on this page --
         [not found] <20260805071839.3423779-1-matthias.goergens@gmail.com>
         [not found] ` <20260805071839.3423779-2-matthias.goergens@gmail.com>
    2026-08-11  8:36   ` [PATCH 1/2] generic/517: expect truthful dedupe progress Zorro Lang
    2026-08-17  9:50 ` [PATCH v2] generic: test FIDEDUPERANGE result reporting with REPORT_PROGRESS Matthias Goergens
    

    This is a public inbox, see mirroring instructions
    for how to clone and mirror all data and code used for this inbox