Skip to content

hlfuse: pass the libfuse options to mfusepy as keyword arguments, fix getattr with a file handle - #10340

Merged
ThomasWaldmann merged 1 commit into
borgbackup:masterfrom
ThomasWaldmann:hlfuse-mfusepy-options
Sep 8, 2026
Merged

hlfuse: pass the libfuse options to mfusepy as keyword arguments, fix getattr with a file handle#10340
ThomasWaldmann merged 1 commit into
borgbackup:masterfrom
ThomasWaldmann:hlfuse-mfusepy-options

Conversation

@ThomasWaldmann

Copy link
Copy Markdown
Member

hlfuse.py called hlfuse.FUSE(self, mountpoint, options, foreground=True, use_ino=True), but mfusepy's third positional parameter is raw_fi, not the libfuse option list. Two consequences on master with the mfusepy backend:

  • The libfuse mount options were never applied. The mount is rw instead of ro, default_permissions is missing, and user-given -o options like allow_other are silently ignored (checked in /proc/mounts; another user gets "Permission denied" despite -o allow_other). fsname=borgfs only looked right because mfusepy defaults the fsname to the operations class name, which happens to be borgfs.
  • Reading a file that takes longer than a second fails with EINVAL. raw_fi was accidentally true, which open/read/release rely on (they use the fuse_file_info struct), but getattr with a file handle received the struct too and used it as a dict key: TypeError: unhashable type → EINVAL. The kernel sends that fgetattr when a read reaches EOF after the attribute timeout (1 s by default), so e.g. cat of any file that takes more than a second to read ends with cat: ...: Invalid argument.

The fix converts the option list to mfusepy keyword arguments, requests raw_fi=True explicitly and unwraps the file handle in getattr.

The regression test mounts an archive, checks via statvfs that the mount is read-only (this fails on master: the options are dropped), reads 4 KiB of a file, sleeps 1.5 s and then does a raw os.read past EOF (this fails on master with OSError: [Errno 22] Invalid argument). It uses os.read deliberately: a buffered f.read() does an fstat first, which refreshes the attributes without a file handle and hides the bug.

Verified on Linux (Debian 13, libfuse 3.17.2, mfusepy 3.1.1): the new test fails on master for both symptoms and passes with the fix, mount_cmds_test.py passes with BORG_FUSE_IMPL=mfusepy, and after the fix /proc/mounts shows ro,...,default_permissions,allow_other for borg mount -o allow_other.

Found while benchmarking #5110 (the long sequential FUSE reads would not complete without this).

🤖 Generated with Claude Code

@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.86%. Comparing base (366db25) to head (9056ef4).
⚠️ Report is 29 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10340      +/-   ##
==========================================
+ Coverage   87.80%   87.86%   +0.06%     
==========================================
  Files         103      103              
  Lines       18828    18876      +48     
  Branches     2906     2915       +9     
==========================================
+ Hits        16531    16586      +55     
+ Misses       1598     1589       -9     
- Partials      699      701       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

… getattr with a file handle

hlfuse.FUSE(self, mountpoint, options, ...) passed the option list as mfusepy's
third positional parameter, which is raw_fi. Consequences:

- the libfuse options were never applied: the mount was rw instead of ro,
  without default_permissions, and user-given -o options like allow_other
  were silently ignored (fsname only looked right because it defaulted to the
  operations class name, which is "borgfs").
- raw_fi was (accidentally) true, so open/read/release received the
  fuse_file_info struct - which they expect - but getattr with a file handle
  (kernel fgetattr, e.g. for a read reaching EOF after the attribute timeout)
  received the struct too and used it as a dict key: TypeError -> EINVAL.
  Visible as "cat: ...: Invalid argument" for any file that takes more than
  a second (the default attr_timeout) to read.

Now convert the option list to keyword arguments, request raw_fi explicitly
and take the file handle from the fuse_file_info struct in getattr. Add a
regression test that checks the mount is read-only and reads a file to EOF
after the attribute timeout.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ThomasWaldmann
ThomasWaldmann merged commit 67306a3 into borgbackup:master Sep 8, 2026
23 checks passed
@ThomasWaldmann
ThomasWaldmann deleted the hlfuse-mfusepy-options branch September 8, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant