Skip to content

refactor(reflinking): add windows ReFS filesystem support#1576

Merged
s0up4200 merged 10 commits intodevelopfrom
feat/windows-reflink-support
Mar 10, 2026
Merged

refactor(reflinking): add windows ReFS filesystem support#1576
s0up4200 merged 10 commits intodevelopfrom
feat/windows-reflink-support

Conversation

@Audionut
Copy link
Contributor

@Audionut Audionut commented Mar 9, 2026

{"level":"info","instanceID":1,"torrentName":"test.mkv","destDir":"D:\\cross_linked\\tracker\\test--38f12c4a","fileCount":1,"time":"2026-03-09T19:15:48+10:00","message":"[CROSSSEED] Reflink mode: created reflink tree"}
{"level":"info","instanceID":1,"torrentName":"test.mkv","destDir":"D:\\cross_linked\\tracker1\\test--e3e3ee09","fileCount":1,"time":"2026-03-09T19:15:48+10:00","message":"[CROSSSEED] Reflink mode: created reflink tree"}
{"level":"info","instanceID":1,"torrentName":"test.mkv","destDir":"D:\\cross_linked\\tracker2\\test--bbece301","fileCount":1,"time":"2026-03-09T19:15:48+10:00","message":"[CROSSSEED] Reflink mode: created reflink tree"}
fsutil file queryExtentsAndRefCounts "D:\\cross_linked\\tracker\\test--25249e5b\test.mkv"
VCN: 0x0        Clusters: 0x20       LCN: 0x99c3d0   Ref: 0xa
VCN: 0x20       Clusters: 0x10       LCN: 0x99c180   Ref: 0xa
VCN: 0x30       Clusters: 0x10       LCN: 0x9a2aa2   Ref: 0xa
VCN: 0x40       Clusters: 0xe        LCN: 0x99c190   Ref: 0xa
VCN: 0x4e       Clusters: 0x20       LCN: 0x9a28e5   Ref: 0xa
VCN: 0x6e       Clusters: 0x10       LCN: 0x9a2ab2   Ref: 0xa
.....

Summary by CodeRabbit

  • New Features

    • Reflink cloning support added for Windows ReFS volumes.
  • Documentation

    • Reflink docs updated: ReFS explicitly supported on Windows; NTFS unsupported. Base directory must be on the same volume and be a real mount; fallback behavior on reflink failure clarified; Windows-specific notes added.
  • Bug Fixes / Platform Handling

    • Platform checks/builds updated to account for Windows; logging downgraded to a warning when reflink is unsupported.
  • Tests

    • Added Windows-specific tests covering reflink probing, cloning, same-volume and filesystem validations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds Windows ReFS-based reflink support (probe + clone), Windows-specific tests, updates unsupported-platform stubs/tests to treat Windows as runtime-detected, documents Windows ReFS requirements and fallback behavior, and downgrades logging for reflink-unsupported errors in cross-seed service.

Changes

Cohort / File(s) Summary
Documentation
documentation/docs/features/cross-seed/hardlink-mode.md
Document Windows ReFS support: ReFS block cloning used, NTFS unsupported, same-volume & real-mount requirements, symlink-resolution behavior, and fallback-to-regular-mode note.
Windows implementation
pkg/reflinktree/reflink_windows.go
New Windows build-tagged implementation: SupportsReflink(dir) probe, cloneFile with DuplicateExtents chunked cloning, cluster-size handling, tail-copy fallback, volume/FS validation, and Windows API wiring.
Windows tests
pkg/reflinktree/reflink_windows_test.go
New Windows-specific tests that monkeypatch helpers to validate duplicate-extents cloning, tail-copy, failure paths, SupportsReflink probing, same-volume/FS checks, sparse handling, and error wrapping/cleanup.
Platform compatibility & tests
pkg/reflinktree/reflink_unsupported.go, pkg/reflinktree/reflink_test.go
Updated unsupported build tag to exclude Windows; tests now treat Windows as runtime-detected platform and assert explicit unsupported-reason messages. Added internal cloneFile stub returning ErrReflinkUnsupported on unsupported builds.
Cross-seed service logging
internal/services/crossseed/service.go
In processReflinkMode, downgrades logging to warning when reflink is unsupported (ErrReflinkUnsupported) while keeping error level for other reflink failures.

Sequence Diagram

sequenceDiagram
    rect rgba(200,200,255,0.5)
    participant Caller
    end
    rect rgba(200,255,200,0.5)
    participant SupportsReflink
    participant VolumeValidation
    end
    rect rgba(255,200,200,0.5)
    participant CloneFile
    participant DuplicateExtent
    participant CopyTail
    end

    Caller->>SupportsReflink: SupportsReflink(dir)
    SupportsReflink->>VolumeValidation: getVolumeRoot / getFilesystemName / getClusterSize
    alt ReFS detected
        SupportsReflink->>SupportsReflink: create probe files
        SupportsReflink->>CloneFile: cloneFile(probeSrc, probeDst)
        CloneFile->>VolumeValidation: ensureSameVolume / ensureRefsVolume
        loop per-chunk
            CloneFile->>DuplicateExtent: duplicateExtent(chunk)
            DuplicateExtent-->>CloneFile: OK/Error
        end
        CloneFile->>CopyTail: copyFileTail(if needed)
        CopyTail-->>CloneFile: done
        CloneFile-->>SupportsReflink: success
        SupportsReflink-->>Caller: (true, "")
    else Non-ReFS or error
        SupportsReflink-->>Caller: (false, reason)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement, documentation, hardlink

Suggested reviewers

  • s0up4200

Poem

🐰 I hopped to a ReFS glade so bright,
Blocks cloned fast in morning light,
Chunks and tails in tidy rows,
Tests applaud where the soft wind blows,
A rabbit cheers: reflinks take flight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor(reflinking): add windows ReFS filesystem support' clearly and specifically describes the main change—adding Windows ReFS support to the reflinking system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/windows-reflink-support

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/reflinktree/reflink_windows.go (1)

281-299: Consider pooling the copy buffer for repeated operations.

The 1MB buffer is allocated on every tail copy. If cloneFile is called frequently (e.g., many files in a reflink tree), pooling via sync.Pool could reduce GC pressure. This is a minor optimization and not blocking.

♻️ Optional: Pool the copy buffer
var copyBufferPool = sync.Pool{
	New: func() any { return make([]byte, copyBufferSize) },
}

func copyFileTail(srcFile, dstFile *os.File, offset, length int64) error {
	// ... seek logic unchanged ...

	buffer := copyBufferPool.Get().([]byte)
	defer copyBufferPool.Put(buffer)

	copied, err := io.CopyBuffer(dstFile, io.LimitReader(srcFile, length), buffer)
	// ... rest unchanged ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/reflinktree/reflink_windows.go` around lines 281 - 299, The copyFileTail
function allocates a 1MB buffer on each call which can increase GC pressure when
cloning many files; introduce a package-level sync.Pool (e.g., copyBufferPool)
whose New returns make([]byte, copyBufferSize) and replace the local make call
with buffer := copyBufferPool.Get().([]byte) and defer
copyBufferPool.Put(buffer) so io.CopyBuffer reuses buffers; keep the existing
seek and copy logic and ensure the pool is declared alongside copyBufferSize.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/reflinktree/reflink_windows.go`:
- Around line 281-299: The copyFileTail function allocates a 1MB buffer on each
call which can increase GC pressure when cloning many files; introduce a
package-level sync.Pool (e.g., copyBufferPool) whose New returns make([]byte,
copyBufferSize) and replace the local make call with buffer :=
copyBufferPool.Get().([]byte) and defer copyBufferPool.Put(buffer) so
io.CopyBuffer reuses buffers; keep the existing seek and copy logic and ensure
the pool is declared alongside copyBufferSize.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ea89af54-f0c3-4467-a9e5-2dc91a2112fe

📥 Commits

Reviewing files that changed from the base of the PR and between 7aadde7 and ff9bffd.

📒 Files selected for processing (5)
  • documentation/docs/features/cross-seed/hardlink-mode.md
  • pkg/reflinktree/reflink_test.go
  • pkg/reflinktree/reflink_unsupported.go
  • pkg/reflinktree/reflink_windows.go
  • pkg/reflinktree/reflink_windows_test.go

@Audionut Audionut added cross-seed area/backend Backend changes labels Mar 9, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/reflinktree/reflink_windows.go`:
- Around line 84-107: Open the source file before calling os.Stat so the
metadata and subsequent clone operate on the same handle: replace the current
os.Stat(src) call with opening the file first (os.Open) to produce srcFile, then
call srcFile.Stat() to populate srcInfo; keep the existing error handling
semantics (wrap errors with fmt.Errorf like "open source: %w" and "stat source:
%w"), ensure srcFile is closed appropriately, and leave the later calls to
ensureSameVolume and ensureRefsVolume unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9cb5b352-7be8-4bf6-99e7-6e8e80c8f891

📥 Commits

Reviewing files that changed from the base of the PR and between ff9bffd and 3dfdddd.

📒 Files selected for processing (1)
  • pkg/reflinktree/reflink_windows.go

Audionut added 2 commits March 9, 2026 20:10
Make SupportsReflink write a probe file sized to at least one ReFS
cluster plus a tail byte, so the probe cannot pass via tail-copy alone.

This ensures the Windows reflink capability check actually invokes
FSCTL_DUPLICATE_EXTENTS_TO_FILE instead of returning a false positive
for small probe files.

Also tighten the Windows probe test to assert both the duplicate-extent
call and tail-copy path are exercised.
@Audionut Audionut requested a review from s0up4200 March 9, 2026 10:36
Audionut added 2 commits March 9, 2026 23:45
Resolve Windows source symlinks before cloning so reflinks operate on the
real file path instead of the reparse point.

Detect sparse ReFS source files, mark destination files sparse, and resize
them with SetFilePointerEx/SetEndOfFile before duplicating extents.

Add Windows test coverage for symlink resolution, sparse destination
preparation, and related error handling.
…tination parents

Improve Windows reflink behavior for folder/mountpoint edge cases and clearer fallback semantics.

- resolve destination parent path before same-volume/filesystem checks in `cloneFile`
  so junction/symlink/mountpoint targets are validated against the actual backing path
- map `windows.ERROR_NOT_SUPPORTED` from duplicate extents to `ErrReflinkUnsupported`
  for consistent unsupported-capability handling
- downgrade cross-seed reflink-create log severity to `warn` when the error is
  `ErrReflinkUnsupported` (expected fallback scenario)
- add Windows reflink tests for:
  - resolved destination-parent volume checks
  - unsupported duplicate-extent error mapping
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/reflinktree/reflink_windows_test.go (1)

35-39: Consider extracting duplicated cloneCall struct.

The cloneCall struct is defined identically in two tests. While acceptable in test code, extracting it to file scope would reduce duplication.

♻️ Suggested refactor
+type cloneCall struct {
+	sourceOffset int64
+	targetOffset int64
+	byteCount    int64
+}
+
 func TestCloneFile_UsesDuplicateExtentsAndCopiesTail(t *testing.T) {
 	// ...
-	type cloneCall struct {
-		sourceOffset int64
-		targetOffset int64
-		byteCount    int64
-	}
 	var cloneCalls []cloneCall

Apply similarly in TestCloneFile_ResolvesSourceSymlinkBeforeClone.

Also applies to: 514-518

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/reflinktree/reflink_windows_test.go` around lines 35 - 39, Extract the
duplicated cloneCall struct into a single package-level (file-scope) declaration
so both tests reuse it: remove the local type definitions inside
TestCloneFile_ResolvesSourceSymlinkBeforeClone and the other test, add one
top-level "type cloneCall struct { sourceOffset int64; targetOffset int64;
byteCount int64 }" in reflink_windows_test.go (above the tests), and update the
tests to reference that single cloneCall type; ensure the name and field types
remain identical so existing test code compiles without other changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/services/crossseed/service.go`:
- Around line 11084-11088: The current logic downgrades logEvent to log.Warn
whenever errors.Is(err, reflinktree.ErrReflinkUnsupported) is true, but that
also matches wrapped errors (e.g., when rollback also failed); change the guard
so you only downgrade when ErrReflinkUnsupported is the sole/terminal error:
check that errors.Is(err, reflinktree.ErrReflinkUnsupported) AND
errors.Unwrap(err) == nil (no further wrapped error) before setting logEvent =
log.Warn(); otherwise keep logEvent as log.Error() so rollback failures stay
logged as errors. Ensure you reference the existing variables err, logEvent and
the constant reflinktree.ErrReflinkUnsupported in the updated condition.

---

Nitpick comments:
In `@pkg/reflinktree/reflink_windows_test.go`:
- Around line 35-39: Extract the duplicated cloneCall struct into a single
package-level (file-scope) declaration so both tests reuse it: remove the local
type definitions inside TestCloneFile_ResolvesSourceSymlinkBeforeClone and the
other test, add one top-level "type cloneCall struct { sourceOffset int64;
targetOffset int64; byteCount int64 }" in reflink_windows_test.go (above the
tests), and update the tests to reference that single cloneCall type; ensure the
name and field types remain identical so existing test code compiles without
other changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 21370664-ed37-4205-b466-4bf5ceaf4467

📥 Commits

Reviewing files that changed from the base of the PR and between 01f3126 and b6f2d35.

📒 Files selected for processing (3)
  • internal/services/crossseed/service.go
  • pkg/reflinktree/reflink_windows.go
  • pkg/reflinktree/reflink_windows_test.go

@s0up4200 s0up4200 merged commit 7899cc8 into develop Mar 10, 2026
24 of 25 checks passed
@s0up4200 s0up4200 deleted the feat/windows-reflink-support branch March 10, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants