Implement FIDEDUPERANGE via block cloning#18745
Draft
MorganaFuture wants to merge 6 commits into
Draft
Conversation
Factor the offset-independent checks of zfs_clone_range() into zfs_clone_range_precheck(), and the range-locked clone loop into zfs_clone_range_locked(). This is a pure refactor with no functional change: zfs_clone_range() runs the same checks in the same order and clones identically. The split lets an upcoming FIDEDUPERANGE implementation reuse both halves and run a byte comparison under the same range locks, instead of duplicating the clone machinery. Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com>
Add zfs_dedupe_range(), the common back end for the Linux FIDEDUPERANGE ioctl. It validates the request with FIDEDUPERANGE semantics (the range must lie within the source, must not extend the destination, and may be shortened to the destination EOF and to a block boundary only when the caller allows it), takes a reader range lock on the source and a writer range lock on the destination, and compares the two ranges byte for byte via the DMU. If they match it clones the source blocks over the destination with zfs_clone_range_locked() without dropping the locks, so the blocks that get shared are exactly the bytes that were compared; concurrent writers, which also take the destination writer lock, cannot change the data in between. If the ranges differ nothing is cloned and the caller is told so. The comparison reads committed data through dmu_read(), the same data zfs_clone_range_locked() clones, and dirty blocks are handled by the existing block-cloning EAGAIN/txg-wait path. Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com>
Route the FIDEDUPERANGE ioctl to zfs_dedupe_range(). Both the modern remap_file_range(REMAP_FILE_DEDUP) path and the pre-4.20 dedupe_file_range fop now call a shared zpl_dedupe_file_range_impl(), which takes the inode locks, invokes zfs_dedupe_range(), and maps the result: the number of bytes deduped when the ranges match, -EBADE when they differ (which the VFS reports as FILE_DEDUPE_RANGE_DIFFERS), or the negative errno on failure. REMAP_FILE_CAN_SHORTEN is passed through so the modern interface may shorten the request to the destination EOF or a block boundary. This lets out-of-band dedupe tools such as duperemove share identical blocks on ZFS through block cloning, without the memory cost of the DDT. Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com> Closes openzfs#11065
Add block_cloning tests for the FIDEDUPERANGE ioctl: whole-file dedupe shares every block, differing files are left untouched, a partial request shares only the matching block, and the ioctl fails when block cloning is disabled. Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com>
Three issues found while reviewing the FIDEDUPERANGE series: - zfs_clone_range() and zfs_dedupe_range() cached outzfsvfs->z_log in their declarations, before zfs_enter_two(). A concurrent suspend/resume (zfs rollback, zfs receive -F) tears down and recreates the zilog, so a thread that read the pointer early could zil_commit() a stale or freed zilog after it finally entered. Read z_log only once inside the entered region, like zfs_clone_range_locked() already does. - A dedupe does not change file content, yet it reused zfs_clone_range_locked() and so bumped the destination's mtime/ctime and stripped its setid bits. That contradicts the Linux REMAP_FILE_DEDUP convention, where __generic_remap_file_range_prep() skips file_modified(), and breaks tools that rely on dedupe being invisible (rsync/backup incrementals, suid binaries). Pass a dedup flag into the locked helper so it leaves the timestamps and setid bits alone. - zfs_dedupe_range() aligned the request down to a block boundary before comparing it. With a large recordsize a sub-block request rounds away to nothing and the ranges were then reported identical without any bytes being compared, so FIDEDUPERANGE could answer SAME for data that differs. Compare the whole requested range first, then align it down for the clone; a differing trailing sub-block now correctly reports DIFFERS. Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com> Closes openzfs#11065
Add two block_cloning tests for the corrected FIDEDUPERANGE behavior: - block_cloning_fideduperange_mtime: a successful dedupe shares the blocks but leaves the destination's mtime and ctime unchanged. - block_cloning_fideduperange_subblock: a dedupe request smaller than the record size whose bytes differ must fail (DIFFERS) and share nothing, rather than being silently rounded away and reported as identical. Also mark block_cloning_disabled_fideduperange.ksh executable; it was committed 0644 and so would not run under the test harness. Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com> Closes openzfs#11065
198de33 to
f2e5543
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
Closes #11065.
Linux's
FIDEDUPERANGEioctl lets out-of-band dedupe tools (e.g.duperemove,bees) ask the filesystem to share byte-identical file ranges. On ZFS the ioctlreturned
EOPNOTSUPP. This implements it on top of block cloning (BRT), somatching ranges share storage without the memory cost of the dedup table.
Description
zfs_clone_range()into azfs_clone_range_precheck()and azfs_clone_range_locked()helper so the dedupe path can reuse the lockedclone loop.
zfs_dedupe_range(): it compares[inoff, inoff+len)and[outoff, outoff+len)throughdmu_read()and, only if they arebyte-for-byte identical, clones the source blocks over the destination — all
under a single
RL_READER/RL_WRITERrangelock hold, so the bytes that getcloned are exactly the bytes that were compared.
zpl_file_range.c: theremap_file_range(REMAP_FILE_DEDUP)path (>=4.20)and the pre-4.20
dedupe_file_rangefop.-EBADEmaps toFILE_DEDUPE_RANGE_DIFFERS;REMAP_FILE_CAN_SHORTENis honored.mtime/ctime or strip setid bits (matching the Linux
REMAP_FILE_DEDUPconvention where
file_modified()is skipped).boundary, so a differing trailing sub-block reports
DIFFERSrather than beingsilently rounded away and reported identical.
How Has This Been Tested?
New
block_cloningZTS cases: whole-file dedupe shares blocks; differing filesfail and change nothing; single-block partial dedupe; feature-disabled; the
destination's mtime/ctime stay unchanged after a dedupe; and a sub-block
request whose bytes differ reports
DIFFERS.Ran the
block_cloninggroup on Linux (Ubuntu, aarch64,--enable-debug) — thefideduperangetests pass and the existing clone tests are unaffected. BRTsharing was confirmed via
get_same_blocks.This is a draft — a couple of points I'd like reviewer input on before
polishing:
zpl_dedupe_file_range_impl()takes the two inode locks without ordering themby address (the existing
zpl_clone_file_range_impl()has the same pattern);concurrent opposite-direction dedupes could deadlock. I'm happy to add an
address-ordered two-inode lock helper shared by both paths.
TX_CLONE_RANGE, so a crash+replay would restampthe destination mtime/ctime (inherited from the flag-less record). Worth a
dedicated log-record flag?
Types of changes
Checklist:
Signed-off-by.