* Re: [PATCH 1/2] generic/517: expect truthful dedupe progress [not found] ` <20260805071839.3423779-2-matthias.goergens@gmail.com> @ 2026-08-11 8:36 ` Zorro Lang 0 siblings, 0 replies; 4+ messages in thread From: Zorro Lang @ 2026-08-11 8:36 UTC (permalink / raw) To: Matthias Goergens; +Cc: fstests, xfs-list, btrfs-list, Filipe Manana On Wed, Aug 05, 2026 at 03:18:38PM +0800, Matthias Goergens wrote: > The VFS currently reports the requested FIDEDUPERANGE length even when it > shortens the operation to avoid sharing a partial EOF block into the middle > of the destination. Once the kernel reports its actual progress, the first > dedupe in this test reports 131072 of 131172 bytes. xfs_io then retries the > 100-byte remainder, which cannot make progress and returns -EINVAL. The > separate 100-byte request returns the same error. > > Update the golden output for those truthful results. This is the output > change that caused commit 4a57a8400075 ("vf/remap: return the amount of > bytes actually deduplicated") to be reverted. The paired kernel series that > determines the new output is linked from the cover letter. > > The updated test passes on btrfs and XFS with the paired kernel changes. To avoid panic in downstream testing caused by sudden breaking changes in expected behavior, we'd better to reference the commit or patch that introduced the change. Rather than using _fixed_by_kernel_commit helper, _wants_kernel_commit might be more appropriate here. CC'ing Filipe, the original author of this test case, for further review. Thanks, Zorro > > Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com> > --- > tests/generic/517 | 6 ++++++ > tests/generic/517.out | 6 +++--- > 2 files changed, 9 insertions(+), 3 deletions(-) > > diff --git a/tests/generic/517 b/tests/generic/517 > index 3f7027a8..ab5d17e7 100755 > --- a/tests/generic/517 > +++ b/tests/generic/517 > @@ -11,6 +11,12 @@ > . ./common/preamble > _begin_fstest auto quick dedupe clone > > +# The dedupe amounts this test records are only correct once the kernel > +# reports the number of bytes it actually deduplicated rather than the number > +# requested. Both dedupe calls below target a range that does not reach the > +# destination's EOF, so generic_remap_check_len() shortens them under > +# REMAP_FILE_CAN_SHORTEN. > + > # Import common functions. > . ./common/filter > . ./common/reflink > diff --git a/tests/generic/517.out b/tests/generic/517.out > index b9b63207..6ba753d8 100644 > --- a/tests/generic/517.out > +++ b/tests/generic/517.out > @@ -13,7 +13,8 @@ File content before first deduplication: > * > 0786528 ae ae ae ae > 0786532 > -deduped 131172/131172 bytes at offset 65536 > +XFS_IOC_FILE_EXTENT_SAME: Invalid argument > +deduped 131072/131172 bytes at offset 65536 > XXX Bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) > File content after first deduplication and before unmounting: > 0000000 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b > @@ -33,8 +34,7 @@ File content after first unmount: > 0786532 > wrote 100/100 bytes at offset 0 > XXX Bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) > -deduped 100/100 bytes at offset 655360 > -XXX Bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) > +XFS_IOC_FILE_EXTENT_SAME: Invalid argument > File content after second deduplication: > 0000000 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b > * > -- > 2.55.0 > ^ permalink raw reply [flat|nested] 4+ messages in thread
* [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 2026-10-30 8:34 ` [PATCH v3] " Matthias Goergens 1 sibling, 1 reply; 4+ 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] 4+ messages in thread
* [PATCH v3] generic: test FIDEDUPERANGE result reporting with REPORT_PROGRESS 2026-08-17 9:50 ` [PATCH v2] generic: test FIDEDUPERANGE result reporting with REPORT_PROGRESS Matthias Goergens @ 2026-10-30 8:34 ` Matthias Goergens 2026-11-04 16:54 ` Zorro Lang 0 siblings, 1 reply; 4+ messages in thread From: Matthias Goergens @ 2026-10-30 8:34 UTC (permalink / raw) To: fstests; +Cc: zlang, fdmanana, linux-xfs, linux-btrfs, djwong, amir73il Add a raw FIDEDUPERANGE helper that reports each destination's bytes_deduped and status, and a test for the new FILE_DEDUPE_RANGE_REPORT_PROGRESS flag: explicit zero-length success, a request shortened for an interior destination but not for one ending at EOF, a sub-block request that makes no progress, differing contents (status DIFFERS, zero bytes), a per-destination error, rejection of an unknown flag bit, and one unflagged case pinning the legacy reporting of the requested length. The test is skipped on kernels that reject the flag. 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; hence the helper. Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com> --- Christian Brauner applied the kernel series to vfs-7.5.misc on 21 October. Reference the flag commit in _wants_kernel_commit: 847f74d6293b (vfs: add FILE_DEDUPE_RANGE_REPORT_PROGRESS flag to FIDEDUPERANGE). https://lore.kernel.org/all/20261021-rente-privat-mitfiebern-1b76f4650038@brauner/ v3: - reference the applied kernel flag commit - renumber 806 to 810: for-next already uses 806 for an xattr test - use _get_file_block_size instead of _get_block_size (Zorro, on v1) - the kernel side (v5) now has Reviewed-by from Amir and Darrick, the acks Zorro asked to wait for; it reports bytes_deduped = 0 on DIFFERS (the advance hint tested in v2 was dropped), so both DIFFERS cases expect zero - new case: an unknown flag bit is rejected with EINVAL (helper -F) - new case: two small differing files ending at EOF report 0 bytes, status DIFFERS - _notrun on kernels that reject the flag, rather than failing - .gitignore entry moved into alphabetical order Previously tested on 2 October as generic/806 under QEMU on v7.3-rc4 with and without the v5 series, on btrfs, XFS (4k and 1k blocks) and ext4. With the series generic/517 and generic/806 pass on btrfs and both XFS configurations; without it 806 is not run ("kernel does not support FILE_DEDUPE_RANGE_REPORT_PROGRESS") and 517 passes. On ext4 both are not run (no dedupe). Validation refreshed on 30 October against fstests for-next d07dca72: helper builds with -Wall -Wextra -Werror -O2; bash -n passes. The filesystem tests have not been rerun on that base. .gitignore | 1 + src/Makefile | 2 +- src/fideduperange.c | 172 ++++++++++++++++++++++++++++++++++++++++++ tests/generic/810 | 144 +++++++++++++++++++++++++++++++++++ tests/generic/810.out | 9 +++ 5 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 src/fideduperange.c create mode 100755 tests/generic/810 create mode 100644 tests/generic/810.out diff --git a/.gitignore b/.gitignore index cdcfa06..418ef7c 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ tags /src/fake-dump-rootino /src/fault /src/feature +/src/fideduperange /src/fiemap-tester /src/fill /src/fill2 diff --git a/src/Makefile b/src/Makefile index eaf0621..774447e 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 0000000..d8aef98 --- /dev/null +++ b/src/fideduperange.c @@ -0,0 +1,172 @@ +// 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] [-F flags] src src_offset length dest dest_offset " + "[dest dest_offset ...]\n" + " -f: set FILE_DEDUPE_RANGE_REPORT_PROGRESS\n" + " -F: set the raw flags value (e.g. 0x2 for an unknown bit)\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, "fF:")) != -1) { + switch (c) { + case 'f': + range_flags |= FILE_DEDUPE_RANGE_REPORT_PROGRESS; + break; + case 'F': + range_flags |= (__u32)parse_u64(optarg, "flags"); + 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/810 b/tests/generic/810 new file mode 100755 index 0000000..2c09315 --- /dev/null +++ b/tests/generic/810 @@ -0,0 +1,144 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2026 Matthias Goergens. All Rights Reserved. +# +# FS QA Test No. 810 +# +# Check FIDEDUPERANGE result reporting with FILE_DEDUPE_RANGE_REPORT_PROGRESS: +# bytes_deduped is the length actually deduplicated for zero-length, +# shortened and zero-progress requests, and zero for differing requests and +# errors; unknown flag bits are rejected; one unflagged case pins the legacy +# reporting. +# +. ./common/preamble +_begin_fstest auto quick dedupe clone + +. ./common/reflink + +_require_scratch_dedupe +_require_test_program "fideduperange" + +_wants_kernel_commit 847f74d6293b \ + "vfs: add FILE_DEDUPE_RANGE_REPORT_PROGRESS flag to FIDEDUPERANGE" + +_scratch_mkfs >>$seqres.full 2>&1 +_scratch_mount + +block_size=$(_get_file_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 + +# Kernels without the flag reject it with EINVAL before looking at any +# destination. +result=$($here/src/fideduperange -f "$src" 0 0 "$dest_interior" 0 2>&1) +echo "flag probe: $result" >>$seqres.full +if echo "$result" | grep -q "Invalid argument"; then + _notrun "kernel does not support FILE_DEDUPE_RANGE_REPORT_PROGRESS" +fi + +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 the length the filesystem reports as +# deduplicated on SAME, and zero on DIFFERS or an error. + +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=0 status=1" +check_results "differing contents: DIFFERS, zero bytes" "$expected" \ + -f "$src_diff" 0 "$full_length" "$dest_diff" 0 + +# Small differing files ending at EOF: still zero, not the request length. + +$XFS_IO_PROG -f -c "pwrite -S 0x41 0 512" "$SCRATCH_MNT/tiny-src" >>$seqres.full 2>&1 +$XFS_IO_PROG -f -c "pwrite -S 0x42 0 512" "$SCRATCH_MNT/tiny-dst" >>$seqres.full 2>&1 + +expected="destination 0: bytes=0 status=1" +check_results "small-file DIFFERS: zero bytes" "$expected" \ + -f "$SCRATCH_MNT/tiny-src" 0 512 "$SCRATCH_MNT/tiny-dst" 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 + +# An unknown flag bit is rejected before any destination is processed. + +result=$($here/src/fideduperange -F 0x2 "$src" 0 "$full_length" "$dest_interior" 0 2>&1) +echo "unknown flag bit: $result" >>$seqres.full +if echo "$result" | grep -q "Invalid argument"; then + echo "unknown flag bit: EINVAL" +else + _fail "unknown flag bit: expected EINVAL, got: $result" +fi + +# 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/810.out b/tests/generic/810.out new file mode 100644 index 0000000..f88ac62 --- /dev/null +++ b/tests/generic/810.out @@ -0,0 +1,9 @@ +QA output created by 810 +zero length: 0 bytes, SAME +mixed partial request: shortened, full +sub-block request: zero progress, full +differing contents: DIFFERS, zero bytes +small-file DIFFERS: zero bytes +error: bytes zero on EINVAL +unknown flag bit: EINVAL +unflagged legacy reporting: full length base-commit: d07dca72d2455f4ba8c660ad4c008541ccccb707 -- 2.56.0 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v3] generic: test FIDEDUPERANGE result reporting with REPORT_PROGRESS 2026-10-30 8:34 ` [PATCH v3] " Matthias Goergens @ 2026-11-04 16:54 ` Zorro Lang 0 siblings, 0 replies; 4+ messages in thread From: Zorro Lang @ 2026-11-04 16:54 UTC (permalink / raw) To: Matthias Goergens Cc: fstests, fdmanana, linux-xfs, linux-btrfs, djwong, amir73il On Fri, Oct 30, 2026 at 04:34:03PM +0800, Matthias Goergens wrote: > Add a raw FIDEDUPERANGE helper that reports each destination's > bytes_deduped and status, and a test for the new > FILE_DEDUPE_RANGE_REPORT_PROGRESS flag: explicit zero-length success, a > request shortened for an interior destination but not for one ending at > EOF, a sub-block request that makes no progress, differing contents > (status DIFFERS, zero bytes), a per-destination error, rejection of an > unknown flag bit, and one unflagged case pinning the legacy reporting of > the requested length. The test is skipped on kernels that reject the > flag. > > 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; hence the helper. > > Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com> > --- > Christian Brauner applied the kernel series to vfs-7.5.misc on > 21 October. Reference the flag commit in _wants_kernel_commit: > 847f74d6293b (vfs: add FILE_DEDUPE_RANGE_REPORT_PROGRESS flag to > FIDEDUPERANGE). > https://lore.kernel.org/all/20261021-rente-privat-mitfiebern-1b76f4650038@brauner/ Now that the implementation of this feature is clear, let's proceed with merging this test case. > > v3: > - reference the applied kernel flag commit > - renumber 806 to 810: for-next already uses 806 for an xattr test > - use _get_file_block_size instead of _get_block_size (Zorro, on v1) > - the kernel side (v5) now has Reviewed-by from Amir and Darrick, the > acks Zorro asked to wait for; it reports bytes_deduped = 0 on DIFFERS > (the advance hint tested in v2 was dropped), so both DIFFERS cases > expect zero > - new case: an unknown flag bit is rejected with EINVAL (helper -F) > - new case: two small differing files ending at EOF report 0 bytes, > status DIFFERS > - _notrun on kernels that reject the flag, rather than failing > - .gitignore entry moved into alphabetical order > > Previously tested on 2 October as generic/806 under QEMU on v7.3-rc4 > with and without the v5 series, on btrfs, XFS (4k and 1k blocks) and > ext4. With the series generic/517 and > generic/806 pass on btrfs and both XFS configurations; without it 806 is > not run ("kernel does not support FILE_DEDUPE_RANGE_REPORT_PROGRESS") > and 517 passes. On ext4 both are not run (no dedupe). > > Validation refreshed on 30 October against fstests for-next d07dca72: > helper builds with -Wall -Wextra -Werror -O2; bash -n passes. The > filesystem tests have not been rerun on that base. > > .gitignore | 1 + > src/Makefile | 2 +- > src/fideduperange.c | 172 ++++++++++++++++++++++++++++++++++++++++++ > tests/generic/810 | 144 +++++++++++++++++++++++++++++++++++ > tests/generic/810.out | 9 +++ > 5 files changed, 327 insertions(+), 1 deletion(-) > create mode 100644 src/fideduperange.c > create mode 100755 tests/generic/810 > create mode 100644 tests/generic/810.out > > diff --git a/.gitignore b/.gitignore > index cdcfa06..418ef7c 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -86,6 +86,7 @@ tags > /src/fake-dump-rootino > /src/fault > /src/feature > +/src/fideduperange > /src/fiemap-tester > /src/fill > /src/fill2 > diff --git a/src/Makefile b/src/Makefile > index eaf0621..774447e 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 \ This part might conflict with a patch ready to be merged ('xfstests: Unify the build targets under LINUX_TARGET'). Could you try rebasing onto the 'patches-in-queue' branch in your next version? That way, I can merge it directly later. > 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 0000000..d8aef98 > --- /dev/null > +++ b/src/fideduperange.c > @@ -0,0 +1,172 @@ > +// 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 Perhaps these two definitions could be moved into the "#ifndef FIDEDUPERANGE" block below. > + > +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]; How about replacing this GCC zero-length array "info[0]" with C standard flexible array "info[]" ? > +}; Regarding these two *_local structures, I've considered a few alternatives, such as: AC_CHECK_MEMBER([struct file_dedupe_range.flags], [AC_DEFINE([HAVE_FILE_DEDUPE_RANGE_FLAGS] ... What do you or others think? Thanks, Zorro > + > +#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] [-F flags] src src_offset length dest dest_offset " > + "[dest dest_offset ...]\n" > + " -f: set FILE_DEDUPE_RANGE_REPORT_PROGRESS\n" > + " -F: set the raw flags value (e.g. 0x2 for an unknown bit)\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, "fF:")) != -1) { > + switch (c) { > + case 'f': > + range_flags |= FILE_DEDUPE_RANGE_REPORT_PROGRESS; > + break; > + case 'F': > + range_flags |= (__u32)parse_u64(optarg, "flags"); > + 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/810 b/tests/generic/810 > new file mode 100755 > index 0000000..2c09315 > --- /dev/null > +++ b/tests/generic/810 > @@ -0,0 +1,144 @@ > +#! /bin/bash > +# SPDX-License-Identifier: GPL-2.0 > +# Copyright (c) 2026 Matthias Goergens. All Rights Reserved. > +# > +# FS QA Test No. 810 > +# > +# Check FIDEDUPERANGE result reporting with FILE_DEDUPE_RANGE_REPORT_PROGRESS: > +# bytes_deduped is the length actually deduplicated for zero-length, > +# shortened and zero-progress requests, and zero for differing requests and > +# errors; unknown flag bits are rejected; one unflagged case pins the legacy > +# reporting. > +# > +. ./common/preamble > +_begin_fstest auto quick dedupe clone > + > +. ./common/reflink > + > +_require_scratch_dedupe > +_require_test_program "fideduperange" > + > +_wants_kernel_commit 847f74d6293b \ > + "vfs: add FILE_DEDUPE_RANGE_REPORT_PROGRESS flag to FIDEDUPERANGE" > + > +_scratch_mkfs >>$seqres.full 2>&1 > +_scratch_mount > + > +block_size=$(_get_file_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 > + > +# Kernels without the flag reject it with EINVAL before looking at any > +# destination. > +result=$($here/src/fideduperange -f "$src" 0 0 "$dest_interior" 0 2>&1) > +echo "flag probe: $result" >>$seqres.full > +if echo "$result" | grep -q "Invalid argument"; then > + _notrun "kernel does not support FILE_DEDUPE_RANGE_REPORT_PROGRESS" > +fi > + > +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 the length the filesystem reports as > +# deduplicated on SAME, and zero on DIFFERS or an error. > + > +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=0 status=1" > +check_results "differing contents: DIFFERS, zero bytes" "$expected" \ > + -f "$src_diff" 0 "$full_length" "$dest_diff" 0 > + > +# Small differing files ending at EOF: still zero, not the request length. > + > +$XFS_IO_PROG -f -c "pwrite -S 0x41 0 512" "$SCRATCH_MNT/tiny-src" >>$seqres.full 2>&1 > +$XFS_IO_PROG -f -c "pwrite -S 0x42 0 512" "$SCRATCH_MNT/tiny-dst" >>$seqres.full 2>&1 > + > +expected="destination 0: bytes=0 status=1" > +check_results "small-file DIFFERS: zero bytes" "$expected" \ > + -f "$SCRATCH_MNT/tiny-src" 0 512 "$SCRATCH_MNT/tiny-dst" 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 > + > +# An unknown flag bit is rejected before any destination is processed. > + > +result=$($here/src/fideduperange -F 0x2 "$src" 0 "$full_length" "$dest_interior" 0 2>&1) > +echo "unknown flag bit: $result" >>$seqres.full > +if echo "$result" | grep -q "Invalid argument"; then > + echo "unknown flag bit: EINVAL" > +else > + _fail "unknown flag bit: expected EINVAL, got: $result" > +fi > + > +# 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/810.out b/tests/generic/810.out > new file mode 100644 > index 0000000..f88ac62 > --- /dev/null > +++ b/tests/generic/810.out > @@ -0,0 +1,9 @@ > +QA output created by 810 > +zero length: 0 bytes, SAME > +mixed partial request: shortened, full > +sub-block request: zero progress, full > +differing contents: DIFFERS, zero bytes > +small-file DIFFERS: zero bytes > +error: bytes zero on EINVAL > +unknown flag bit: EINVAL > +unflagged legacy reporting: full length > > base-commit: d07dca72d2455f4ba8c660ad4c008541ccccb707 > -- > 2.56.0 > ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-04 16:54 UTC | newest]
Thread overview: 4+ 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
2026-10-30 8:34 ` [PATCH v3] " Matthias Goergens
2026-11-04 16:54 ` Zorro Lang
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox