mount: support Windows using WinFsp (via mfusepy), fixes #2316 - #10391
Merged
ThomasWaldmann merged 2 commits intoSep 19, 2026
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #10391 +/- ##
==========================================
+ Coverage 88.19% 88.22% +0.03%
==========================================
Files 103 103
Lines 18822 18865 +43
Branches 2919 2931 +12
==========================================
+ Hits 16600 16644 +44
+ Misses 1548 1544 -4
- Partials 674 677 +3 ☔ View full report in Codecov by Harness. |
ThomasWaldmann
force-pushed
the
mount-winfsp-2316
branch
from
September 19, 2026 11:06
9576aa6 to
0cebe19
Compare
mfusepy already knows how to talk to the FUSE 2 API of WinFsp, so this is about the borg side: - mountpoint: WinFsp mounts on an unused drive (X:) or creates (and later removes) the mountpoint directory, so it must not exist yet. - there is no fork: borg mount always stays in the foreground. Ctrl-C or Ctrl-Break unmount. borg umount is not supported there. - WinFsp ignores the "ro" mount option, a read-only volume has to be requested from the init callback (FSP_FUSE_CAP_READ_ONLY). - WinFsp makes a security descriptor from uid / gid / mode and checks all access against it. The archived uids / gids do not mean anything on Windows, so the uid and gid mount options are left to WinFsp and default to -1 (the user who mounts). - volname gives the volume label (default: borgfs). - st_birthtime is given to mfusepy, WinFsp uses it as the creation time. - readlink of something that is not a symlink fails with EINVAL (WinFsp tries that for "/" to find out whether symlinks are supported). Tests: on Windows, fuse_mount runs borg mount as a child process and stops it like Ctrl-Break does. WinFsp does not support hard link counts. With mfusepy 3.1.1, symlinks show up as regular files, because WinFsp has getdir and readlink in a different order in struct fuse_operations than libfuse has. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
So the borg mount tests also run on Windows and the Windows binary can mount (if WinFsp is installed). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ThomasWaldmann
force-pushed
the
mount-winfsp-2316
branch
from
September 19, 2026 12:18
0cebe19 to
e6de95d
Compare
Closed
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.
Fixes #2316.
borg mountnow works on Windows, using WinFsp via mfusepy. mfusepy already knows how to talk to WinFsp's FUSE 2 API (it findswinfsp-x64.dllvia the registry), so this is about the borg side.What changes on Windows
X:,X:\is accepted, too) or on a directory that does not exist yet (WinFsp creates it when mounting and removes it when unmounting).do_mountchecks for that instead of "existing, writable directory".fork):borg mountalways stays in the foreground. Ctrl-C and Ctrl-Break unmount cleanly (rc 0, mountpoint gone, repository lock released).borg umountis not supported there and says so: WinFsp has nothing likefusermount -u(checkedfsptool/launchctl), so the way to unmount is to stopborg mount(Ctrl-C / Ctrl-Break).romount option, a read-only volume has to be requested from the init callback (conn_info.want |= FSP_FUSE_CAP_READ_ONLY, via mfusepy'sinit_with_config).0600file of uid 1000), so on Windows theuid/gidmount options are left to WinFsp (which then overrides owner / group of all files) and default to-1= the user who mounts.UserName=/GroupName=also work.volname(defaultborgfsfor a drive,<dirname> (borgfs)otherwise), like we already do for macFUSE.st_birthtimeis given to mfusepy now (WinFsp uses it unconditionally as the creation time; harmless elsewhere, mfusepy ignores it if the platform'sstruct statdoes not have it).readlinkof something that is not a symlink fails with EINVAL, like readlink(2). WinFsp tries that for/when mounting to find out whether symlinks are supported - that gave an "Uncaught exception" traceback in the log.Known limitations (documented in
borg mount --help)st_nlinkis always 1 (inode numbers of hard links are equal, though).struct fuse_operationshasgetattr, getdir, readlink, while libfuse (and thus mfusepy 3.1.1) hasgetattr, readlink, getdir. So WinFsp does not find ourreadlink, decides "no symlink support" and shows symlinks as regular files. With the two fields swapped for Windows in mfusepy, file / directory / dangling symlinks all work (verified). The tests here cope with both mfusepy variants. Fix for mfusepy: [fix] Windows: WinFsp has getdir before readlink in fuse_operations mxmlnkn/mfusepy#52. The WinFsp author confirmed that the different order "was an early accident that unfortunately no longer can be fixed due to backwards compatibility" (FUSE 2 API: struct fuse_operations has getdir before readlink (libfuse: readlink before getdir) - is this intended? winfsp/winfsp#693).Tests
fuse_mount()has a Windows branch (winfsp_mount()): it runsborg mount --foregroundas a child process in an own process group and stops it withCTRL_BREAK_EVENT, i.e. like a user would do it.GetVolumeInformationW(noos.statvfs), NTFS symlinks havest_size0,test_migrate_lock_aliveneeds daemonizing.parse_mount_optionstests (they run on all platforms),readlinkEINVAL test for the mfusepy adapter, Windows-only test for the mountpoint /borg umounterrors.choco install winfsp) and themfusepyextra, so the mount tests run there and the Windows binary bundles mfusepy. This works on the GitHub runner: the mount tests ran there (19 passed, 4 skipped: ACLs are Linux-only, lock migration needs daemonizing).How this was tested
Windows 11 Pro, WinFsp 2.1, MSYS2 UCRT64 (Python 3.14), mfusepy 3.1.1:
fsutil fsinfo volumeinfo), volume label, ownership, Ctrl-C / Ctrl-Break / kill behaviour, mountpoint validation errors.mount_cmds_test.py,vfs_test.py,fuse_test.py: 42 passed with-n4, withpython -m borgand with a PyInstaller-builtborg.exe(one-dir and single-file both mount and unmount fine), with released and with fixed mfusepy.Not tested: using the mount from the Explorer of an interactive desktop session (everything was done via ssh).
🤖 Generated with Claude Code