mount: support Windows using WinFsp (via mfusepy), fixes #2316 - #10391
Open
ThomasWaldmann wants to merge 2 commits into
Open
ThomasWaldmann wants to merge 2 commits into
ThomasWaldmann wants to merge 2 commits into
Conversation
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 <[email protected]>
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 <[email protected]>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #10391 +/- ##
==========================================
- Coverage 88.19% 88.19% -0.01%
==========================================
Files 103 103
Lines 18822 18855 +33
Branches 2919 2930 +11
==========================================
+ Hits 16600 16629 +29
Misses 1548 1548
- Partials 674 678 +4 ☔ View full report in Codecov by Harness. |
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 borg would need an own way to tell a runningborg mountto stop. There is a TODO for that.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. I'll take that to mfusepy separately.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.choco install winfsp) and themfusepyextra, so the mount tests run there and the Windows binary bundles mfusepy. This CI part is untested until it runs here.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