Skip to content

fix(fuse): switch to hanwen/go-fuse#11272

Open
lidel wants to merge 51 commits intomasterfrom
feat/consolidate-fuse-tests
Open

fix(fuse): switch to hanwen/go-fuse#11272
lidel wants to merge 51 commits intomasterfrom
feat/consolidate-fuse-tests

Conversation

@lidel
Copy link
Copy Markdown
Member

@lidel lidel commented Apr 3, 2026

This PR migrates to hanwen/go-fuse for reasons noted in #11249 (review) and #11272 (comment)
The library is much better, and we can leverage modern FUSE/kernel APIs to map IPFS/MFS/UnixFS abstractions more closely to a real filesystem.

It also fixes over a decade of FUSE bugs, making it much more useful.

Test consolidation

Consolidate all FUSE tests so they actually run in CI.

  • Move test/cli/fuse_test.go into a test/cli/fuse/ sub-package
  • Convert sharness shell tests (t0030, t0031, t0032) into Go integration tests
  • Split make test_fuse into test_fuse_unit (./fuse/...) and test_fuse_cli (./test/cli/fuse/...)
  • Set TEST_FUSE=0 in test_cli so FUSE tests skip in the cli-tests CI job
  • Run both unit and CLI FUSE tests in the fuse-tests CI job
    • 👉 this allows us to run FUSE test on non-linux platforms too, similar to migrations
  • Delete the sharness FUSE tests (were always skipped in CI anyway)
  • Add cross-reference comments between unit tests and end-to-end tests

FUSE library migration

Replaced unmaintained bazil.org/fuse with actively maintained hanwen/go-fuse v2. This fixes two architectural deadlocks that bazil could never solve: ftruncate and fsync now work because go-fuse passes
the open file handle to Setattr/Fsync, letting us use the existing write descriptor.

Fixes

  • fsync works: editors (vim, emacs) and databases that call fsync no longer get a silent no-op
  • ftruncate works: rsync --inplace and tools that shrink/grow files via ftruncate no longer get ENOTSUP
  • rename-over-existing works: rsync and atomic-save editors can rename onto an existing file
  • chmod/touch no longer drops file content: setting mode or mtime with StoreMtime/StoreMode previously replaced the DAG node without preserving content links
  • symlinks on writable mounts: ln -s works on /mfs and /ipns, stored as UnixFS TSymlink nodes
  • readdir reports symlinks correctly: ls -l and find -type l see the right file type
  • faster reads on /ipfs: DagReader is reused across sequential Read calls instead of re-resolving from the root each time
  • killing stuck cat works: interrupting a read cancels in-flight block fetches
  • external unmount detected: fusermount -u from outside the daemon marks the mount inactive
  • fd leak on Open error: file descriptor is now closed on error paths
  • proper error returns from Unlink/Rmdir: returns the actual MFS error instead of always ENOENT
  • no more panic on unknown node type: returns EIO instead
  • deprecated ipfs_cid xattr normalized: returns the CID (same as ipfs.cid) with an error log, instead of ENOATTR

New features

  • Mounts.StoreMtime and Mounts.StoreMode: opt-in config flags to persist mtime and POSIX mode in UnixFS metadata on writable mounts
  • Setattr on directories: chmod and touch work on directories (needed by tar, rsync)
  • Setattr on symlinks: mtime persistence for symlinks (needed by rsync)
  • ipfs.cid xattr on all mounts: extended attribute exposing the node's CID
  • CAP_ATOMIC_O_TRUNC: kernel sends O_TRUNC in Open instead of a separate SETATTR, avoiding a second write descriptor deadlock
  • macOS mount options: volname, noapplexattr, noappledouble set automatically

Architecture

  • Extracted shared writable types (Dir, FileInode, FileHandle, Symlink) into fuse/writable/ package, used by both /mfs and /ipns
  • Shared writable test suite (fusetest.RunWritableSuite) with ~30 scenarios run by both mounts
  • CLI integration tests moved to test/cli/fuse/ sub-package with new test cases (sharded dirs, IPNS resolution, publish-while-mounted)
  • Build tags narrowed to (linux || darwin || freebsd) && !nofuse to match actual go-fuse platform support
  • macOS FUSE check simplified from OSXFUSE 2.x version parsing to macFUSE 4.x path detection
  • fusermount3 handled natively by go-fuse, CI symlink workaround removed
  • make test_fuse split into test_fuse_unit and test_fuse_cli
  • FUSE tests excluded from test_cli via TEST_FUSE=0, run in dedicated fuse-tests CI job

References

Likely fixed (no way to reproduce reporter's exact environment)

lidel added 2 commits April 3, 2026 02:54
Move FUSE integration tests from sharness shell scripts (t0030, t0031,
t0032) and test/cli/fuse_test.go into a dedicated test/cli/fuse/ Go
sub-package, ensuring all FUSE test cases run in CI.

- git mv test/cli/fuse_test.go to test/cli/fuse/ (package fuse)
- convert all sharness FUSE tests to Go subtests under TestFUSE:
  mount failure, IPNS symlink, IPNS NS map resolution, MFS file/dir
  creation, xattr (Linux), files write, add --to-files, file removal,
  nested dirs, publish-while-mounted block, sharded directory reads
- add xattr helpers with build tags (linux/other) using unix.Getxattr
- split make test_fuse into test_fuse_unit (./fuse/...) and
  test_fuse_cli (./test/cli/fuse/...) sub-targets
- set TEST_FUSE=0 in test_cli so FUSE tests skip in cli-tests CI job
- increase fuse-tests CI timeout from 5m to 10m for CLI tests
- delete sharness t0030, t0031, t0032 (were always skipped in CI)
Add cross-reference comments between the unit tests in fuse/readonly/,
fuse/ipns/, fuse/mfs/ and the end-to-end CLI tests in test/cli/fuse/.
Also fix AGENTS.md to use a temp dir for fusermount symlink instead of
sudo.
@lidel lidel added the skip/changelog This change does NOT require a changelog entry label Apr 3, 2026
On shared self-hosted runners, leftover mount points from previous
runs can exhaust the kernel FUSE mount limit.

- add job-level concurrency group so only one fuse-tests runs at a time
- lazy-unmount stale /tmp/fusetest* mounts before running tests
@lidel lidel marked this pull request as ready for review April 3, 2026 01:39
@lidel lidel requested a review from a team as a code owner April 3, 2026 01:39
@lidel lidel marked this pull request as draft April 3, 2026 13:57
The Flush handler wrapped fi.fi.Flush() in a goroutine so it could
return early when the FUSE context was canceled. But the goroutine
kept running in the background, and when Release arrived it called
Close on the same file descriptor concurrently. The two paths both
entered DagModifier.Sync, racing on its internal write buffer and
causing a nil pointer panic.

The fix is to call Flush directly without a goroutine. The MFS flush
cannot be safely canceled mid-operation anyway, so the goroutine
only added the illusion of cancellation while leaking work and
masking the real error.

Also bumps boxo to pick up the matching defense-in-depth fix that
serializes FileDescriptor.Flush and Close with a mutex.
@lidel lidel force-pushed the feat/consolidate-fuse-tests branch from d60bb04 to a588c23 Compare April 3, 2026 19:28
lidel added 7 commits April 3, 2026 22:33
bazil/fuse dispatches each FUSE request in its own goroutine.
The IPNS File handle had no synchronization, so concurrent
Read/Write/Flush/Release calls could overlap on the underlying
DagModifier which is not safe for concurrent use.

Add sync.Mutex to File, matching the pattern already used by the
MFS FileHandler.
bazil/fuse only dispatches Forget to nodes via the NodeForgetter
interface. File is a handle, not a node, so this method was never
called. The /mfs mount has no equivalent.
The /mfs mount flushes the directory after Unlink and Rename so
changes propagate to the MFS root immediately. The /ipns mount
did not, leaving mutations pending until an unrelated flush.

Also add an empty-directory check before removing directories,
matching the /mfs mount's safety check.
New files created via the /ipns FUSE mount now inherit the CID
builder from their parent directory, preventing CIDv0 nodes from
appearing inside a CIDv1 tree.

The directory is also flushed after AddChild so the new entry
propagates to the MFS root immediately, matching the /mfs mount.
Cover the file removal path and the empty-directory safety check
added in the previous commit. TestRemoveFile verifies a created
file can be removed and is gone afterwards. TestRemoveNonEmptyDirectory
verifies that rmdir on a directory with children fails, and succeeds
once the children are removed first.
All three FUSE mounts now read mode and mtime from UnixFS metadata
when present, falling back to POSIX defaults when absent. Most IPFS
data does not include this optional metadata.

Writing mode and mtime is opt-in via two new config flags:
- Mounts.StoreMtime: persist mtime on file create and open-for-write
- Mounts.StoreMode: persist mode on chmod

Other changes in this commit:
- align default file/dir modes across /ipns and /mfs to 0644/0755
- share mode constants via fuse/mount/mode.go
- convert Mounts.FuseAllowOther from bool to Flag for consistency
- add Setattr to /ipns FileNode and /mfs File for chmod and touch
- move dead File.Setattr from IPNS handle to FileNode (node)
- bump boxo for Directory.Mode() and Directory.ModTime() getters
All three FUSE mounts now expose the node's CID via the ipfs.cid
extended attribute on both files and directories.

The /mfs mount also accepts the old ipfs_cid name for backward
compatibility. The /ipfs mount previously had a stub that returned
nil for all xattrs; it now returns the correct CID.

The xattr name follows the convention used by CephFS (ceph.*),
Btrfs (btrfs.*), and GlusterFS (glusterfs.*).
@lidel lidel force-pushed the feat/consolidate-fuse-tests branch from 03dac69 to 6ba3437 Compare April 4, 2026 03:27
@lidel
Copy link
Copy Markdown
Member Author

lidel commented Apr 6, 2026

Status update

Started as test consolidation, grew into a broader FUSE fix-up after CI exposed a race condition panic.

What changed

Fixed the panic: The IPNS Flush handler leaked a background goroutine that raced with Release on the same file descriptor, causing a nil-pointer crash. Removed the goroutine (the flush can't be canceled anyway) and added a mutex in boxo as safety net.

Brought /ipns up to /mfs quality: added mutex on file handle ops, flushing after directory mutations, CID builder inheritance on file creation, rmdir safety check. Removed dead code that bazil/fuse never called.

UnixFS mode and mtime: all three mounts now show POSIX mode and mtime from UnixFS when present. New opt-in config flags (Mounts.StoreMtime, Mounts.StoreMode) let writable mounts persist them on write/chmod. Off by default since it changes CIDs.

ipfs.cid xattr everywhere: getfattr -n ipfs.cid /ipfs/.../file returns the CID on all mounts.

Tests and CI: dedicated fuse-tests job, coverage for remove, chmod, mtime, default modes, xattr.

What we can't fix with the current FUSE library

bazil.org/fuse (unmaintained since 2020) dispatches FUSE_SETATTR to the inode only. The open file handle is never passed to the handler. This breaks two operations:

  • fsync(fd) needs the open handle to flush its write buffer. Without it, we'd have to open a second writer, which deadlocks (MFS allows one at a time). Currently a no-op. Editors like vim that call fsync after saving don't get confirmation that data hit the DAG until close.

  • truncate(path, size) and ftruncate(fd, size) need the handle for the same reason. Currently returns ENOTSUP. Only open(path, O_TRUNC) works.

Why hanwen/go-fuse would fix this

hanwen/go-fuse v2 passes the file handle to Setattr and runs Fsync on the handle directly. Both operations work without opening a second writer.

It's actively maintained (v2.9.0, Oct 2025), used by gocryptfs and rclone. The stability issues rclone hit in 2021 were fixed in v2.5.1 and don't apply to our use case.

I'll see if we can refactor without investing too much time.

@lidel lidel changed the title test(fuse): consolidate FUSE tests into test/cli/fuse fix(fuse): switch to hanwen/go-fuse Apr 7, 2026
lidel added 7 commits April 7, 2026 02:54
Replace the unmaintained bazil.org/fuse (last commit 2020) with
hanwen/go-fuse v2.9.0, fixing two architectural issues that could
not be solved with the old library.

ftruncate now works: hanwen/go-fuse passes the open file handle to
NodeSetattrer, so Setattr can truncate through the existing write
descriptor instead of trying to open a second one (which deadlocks
on MFS's single-writer lock).

fsync now works: FileFsyncer runs on the handle directly, flushing
the write buffer through the open descriptor. Previously a no-op
because bazil dispatched Fsync to the inode only.

mount package:
- NewMount takes (InodeEmbedder, mountpoint, *fs.Options) instead
  of (fs.FS, mountpoint, allowOther)
- mount/unmount collapses to a single fs.Mount call
- fusermount3 tried before fusermount in ForceUnmount

all three mounts:
- structs embed fs.Inode (hanwen's InodeEmbedder pattern)
- Remove split into Unlink + Rmdir (separate FUSE interfaces)
- ReadDirAll replaced with Readdir returning DirStream
- fillAttr helper shared between Getattr and Lookup responses
- kernel cache invalidation via NotifyContent after Flush
- 1s entry/attr timeout for writable mounts (matches go-fuse
  default, gocryptfs, rclone)
- O_APPEND tracked on file handle, writes seek to end
- build tags standardized to (linux || darwin || freebsd) && !nofuse

tests:
- replaced bazil fstestutil.MountedT with shared fusetest.TestMount
- fixed TestConcurrentRW: channel drain mismatch and missing sync
  between write Close and read start
- added TestFsync, TestFtruncate, TestReadlink, TestSeekRead,
  TestLargeFile, TestRmdir, TestCrossDirRename, TestUnknownXattr
- added StoreMtime disabled/enabled subtests
MFS enforces a single-writer lock, so a leaked write descriptor
blocks all subsequent opens of that file until GC.
Without this, IsActive stays true after `fusermount -u` and
Unmount returns nil instead of ErrNotMounted.
After confirming the child exists, an Unlink failure could be an
IO error. Returning ENOENT would hide the real cause.
Readonly Open now returns a file handle holding a DagReader instead
of recreating one per Read call. Sequential reads no longer
re-traverse the DAG from the root on each kernel request.

All three mounts now use CtxReadFull with the kernel's per-request
context so killing a process mid-read cancels in-flight block
fetches instead of letting them complete uselessly.
- remove dead `_ = mntDir` in TestXattrCID
- comment why immutableAttrCacheTime and mutableCacheTime are var
- add TODO for using IPNS record TTL as cache timeout
The old check tried to verify OSXFUSE >= 2.7.2 to avoid a kernel
panic from 2015. It used sysctl, tried to `go install` a third-party
tool at runtime, and referenced paths that no longer exist.

Replace with a simple check for the macFUSE mount helper, matching
the same paths go-fuse looks for. If neither macFUSE nor OSXFUSE is
found, point the user to the install page.

Also standardize build tags to (linux || darwin || freebsd) && !nofuse
and use strings.ReplaceAll.
@lidel lidel mentioned this pull request Apr 7, 2026
lidel added 2 commits April 7, 2026 03:28
go-fuse's fusermount errors don't include the path, so tools that
check error messages for the mountpoint name couldn't tell which
mount failed.
go-fuse finds fusermount3 natively, no symlink needed. The stale
mount cleanup was for bazil's fstestutil which we no longer use.
IPNS Rename now unlinks the target before AddChild, matching MFS.
Without this, renaming onto an existing name returned "directory
already has entry".

Bump boxo to pick up the flushUp unlinked-entry fix (ipfs/boxo@8ae46d5):
when a file descriptor outlives its directory entry (FUSE RELEASE
racing with RENAME), flushUp no longer re-adds the stale name.

Unskip TestTempFileRename and TestRsyncPattern on both mounts.
lidel added 2 commits April 8, 2026 01:35
boxo@552d8e7 fixes File.setNodeData dropping content links when
updating metadata (mode, mtime). chmod or touch after write no
longer makes the file appear empty.

Unskip TestVimSavePattern on both mounts. Remove debug logging
and temporary test functions added during investigation.
go-fuse does not compile on windows/openbsd/netbsd/plan9. Move
WritableMountCapabilities (which imports go-fuse) from mode.go
(no build tag) to caps.go (platform-gated). Align build tags on
fusetest and core/commands/mount stubs so unsupported platforms
don't pull in go-fuse transitively.
@lidel lidel removed the skip/changelog This change does NOT require a changelog entry label Apr 8, 2026
lidel added 7 commits April 8, 2026 02:38
The doUnmount helper hardcoded fusermount, but systems with only
fuse3 installed have fusermount3. Try fusermount3 first, matching
what go-fuse and our ForceUnmount already do.
Add NodeSymlinker to MFS and IPNS directories. Symlinks are stored
as UnixFS TSymlink nodes in the DAG, the same format used by
`ipfs add` for directories containing symlinks. The readonly /ipfs
mount already rendered existing symlinks; now /mfs and /ipns can
create them too.

The target string is cached at Lookup time to avoid re-parsing the
DAG node on every Readlink call. Symlink permissions are always
0777 per POSIX convention (access control uses the target's mode).
The direct type assertion on newParent could panic if the kernel
passed a non-directory inode. Use a checked assertion with EINVAL
fallback, matching the type-switch pattern in the IPNS mount.
Missing continue after error sends let execution fall through to
nil type assertions (read.(files.File)) that would panic on error.
Also cancel the context before continuing to avoid leaking it.
Abort the directory listing instead of silently omitting the
unretrievable entry. Callers get EIO, which is more honest than
a partial listing that hides missing blocks.
On shared self-hosted runners, leftover mounts from crashed runs
can exhaust the kernel mount_max limit. Lazy-unmount kubo-test
and harness temp mounts before and after tests.
Copy link
Copy Markdown
Contributor

@guillaumemichel guillaumemichel left a comment

Choose a reason for hiding this comment

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

Great work fixing all these FUSE issues! This has been a long-standing blind spot in test coverage, so new tests running in the CI are welcome!

Quickly eyeballed the code changes, didn't see any major flaw. Switching to an actively maintained library and actually running FUSE tests in CI can only be an improvement.

@@ -1,4 +1,4 @@
//go:build !windows && nofuse
//go:build !windows && (nofuse || !(linux || darwin || freebsd))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What are we trying to achieve here? A comment would be welcome

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

good idea, documented these in 819016a

lidel added 6 commits April 8, 2026 15:15
Picks up flushUp unlinked-entry guard and setNodeData content link
preservation.
Add a one-line comment above every //go:build directive explaining
why the constraint exists.

Normalize tag style: positive platform constraints first, then
feature flags/negations. Simplify redundant expressions.
Tools like tar and rsync call utimensat on directories after
extraction. Without Setattr on Dir, this returned ENOTSUP.

Add Setattr to Dir (MFS) and Directory (IPNS) that handles mode
and mtime the same way as the file-level Setattr. When StoreMtime
or StoreMode is disabled the call succeeds silently, matching the
file-level behavior.
- mention that touch and chmod work on both files and directories
- note tar and rsync as practical use cases
- link to UnixFS spec for optional metadata storage
Use files.UnixPermsToModePerms and files.ModePermsToUnixPerms for
converting between FUSE kernel mode (unix 12-bit layout) and Go's
os.FileMode (different bit positions for setuid/setgid/sticky).

The UnixFS spec supports all 12 permission bits, but boxo's MFS
layer (File.Mode, Directory.Mode) exposes only the lower 9. FUSE
mounts are always nosuid so the upper 3 bits would have no effect.

Add TestSetuidBitsStripped to both mounts confirming the behavior.
Wire the backing mfs.File into the FUSE Symlink struct so Setattr
can call SetModTime when StoreMtime is enabled. boxo's File methods
(SetModTime, ModTime) already work on TSymlink nodes since they
operate on the FSNode protobuf without checking the type.

Without Setattr, rsync -a fails with "failed to set times" on
symlinks. Every major FUSE filesystem (gocryptfs, rclone, sshfs,
s3fs) implements Setattr on symlinks for this reason.

Mode is always 0777 per POSIX convention, so chmod requests are
silently accepted but not stored.
@lidel lidel force-pushed the feat/consolidate-fuse-tests branch from 13f9e11 to 05bdd59 Compare April 8, 2026 17:15
lidel added 6 commits April 8, 2026 23:09
Replace panic with log.Errorf + syscall.EIO in IPNS Directory.Lookup
for unexpected MFS node types. Also remove duplicate comment block
on File.Flush.
- fuse.md: replace stale OSXFUSE section with macFUSE, remove
  obsolete go-fuse-version tool, fix broken FreeBSD sudo echo,
  update xattr example to ipfs.cid with CIDv1, add mode/mtime
  section, add unixfs-v1-2025 tip, add debug logging section,
  add TOC, link to hanwen/go-fuse
- changelog: refine bullet wording, link to fuse.md
- config.md: fix double space, update fuse.md link text
- experimental-features.md: fix double space, soften wording
- README.md: add FUSE to features list and docs table
Extract duplicated code from fuse/mfs and fuse/ipns into a shared
fuse/writable package, and consolidate duplicated tests into a
reusable suite in fuse/fusetest.

- fuse/writable: Dir, FileInode, FileHandle, Symlink types with all
  FUSE interface methods, shared by both mounts
- fuse/fusetest: RunWritableSuite with helpers, exercised by both
  mfs and ipns via mount-specific factories
- fix cache invalidation race: NotifyContent in Flush (synchronous)
  in addition to Release (async), so stat after close sees new size
- drop deprecated ipfs_cid xattr, log error guiding users to ipfs.cid
- mfs_unix.go: 632 -> 19 lines (thin wrapper over writable.Dir)
- ipns_unix.go: 795 -> 170 lines (Root + key resolution only)
- mfs_test.go: 1183 -> 95 lines (factory + persistence test)
- ipns_test.go: 1309 -> 162 lines (factory + IPNS-specific tests)
- tests that were only in one mount now run on both
Set volname, noapplexattr, and noappledouble on macOS via
PlatformMountOpts, applied in NewMount so all three mounts
benefit automatically.

- volname: shows mount name in Finder instead of "macfuse Volume 0"
- noapplexattr: suppresses Finder's com.apple.* xattr probes
- noappledouble: prevents ._ resource fork sidecar files
Readdir on writable mounts now checks the underlying DAG node type
for TFile entries, reporting S_IFLNK for symlinks instead of regular
file. This makes ls -l and find -type l work correctly.

- writable: Readdir checks SymlinkTarget for TFile entries
- writablesuite: add SymlinkReaddir regression test
- readonly: add TestReaddirSymlink regression test
- test/cli/fuse: fix stale bazil.org/fuse reference in doc comment
Getxattr for the old "ipfs_cid" name now returns the CID instead of
ENOATTR, keeping existing tooling working during the deprecation
period. A log error is emitted on each access to nudge migration.
@lidel
Copy link
Copy Markdown
Member Author

lidel commented Apr 9, 2026

Changes since b8a0823 (reviewed by @guillaumemichel):

  • chmod and touch work on directories and symlinks (needed by tar, rsync)
  • Proper UnixFS mode conversion, setuid/setgid/sticky bits silently stripped
  • Readdir reports symlinks correctly (ls -l, find -type l work)
  • Deprecated ipfs_cid xattr returns the CID instead of ENOATTR (logs error)
  • Returns EIO instead of panicking on unknown node types
  • Extracted shared writable code into fuse/writable/, eliminating duplication between /mfs and /ipns
  • macOS-specific mount options (volname, noapplexattr, noappledouble)
    • cc @wjmelements, this should make macfuse work pretty ok, including truncations and flush, but I did not test on macOS.
      • iiuc we cant test mac on CI because of their security theatre: brew install --cask macfuse requires a kernel extension approval via System Settings and a reboot 🙃 GitHub Actions macOS runners don't support loading third-party kexts. This is a macFUSE limitation, not something we can work around. The FUSE tests are Linux-only in CI, which should be fine since the go-fuse code paths are the same across platforms (the platform-specific parts are just mount options), but mac YMMV
  • Updated docs for go-fuse migration

I think this is pretty solid as-is, amount of fixed bugs is just crazy. If we have any remaining bugs, we can fix them in future PRs, but this is good enough for 0.41 RC1.

lidel added 3 commits April 9, 2026 02:56
The go-fuse server dispatches each FUSE request in its own goroutine.
On files larger than 128 KB the kernel issues concurrent readahead
Read requests on the same file handle, racing on the shared DagReader's
Seek+CtxReadFull sequence and corrupting its internal state.

Add sync.Mutex to roFileHandle (matching the existing pattern in
writable.FileHandle) and lock in Read and Release.

- fuse/readonly/readonly_unix.go: add mu sync.Mutex to roFileHandle
- fuse/readonly/ipfs_test.go: add TestConcurrentLargeFileRead
- fuse/fusetest/writablesuite.go: add LargeFileConcurrentRead to
  shared writable suite (exercised by both /mfs and /ipns tests)
MFS uses an RWMutex (desclock) that holds RLock for the lifetime of a
read descriptor and requires exclusive Lock for writes. Tools like
rsync --inplace open the same file for reading and writing from
separate processes, deadlocking on this mutex.

For O_RDONLY opens, create a DagReader directly from the current DAG
node instead of going through MFS. The reader gets a point-in-time
snapshot and never touches desclock, so writers proceed independently.

- fuse/writable/writable.go: add roFileHandle with DagReader for
  read-only opens, add DAG field to Config
- fuse/mfs/mfs_unix.go: pass ipfs.DAG to writable Config
- fuse/ipns/ipns_unix.go: pass ipfs.Dag() to writable Config
- fuse/fusetest/writablesuite.go: add ConcurrentReadWrite test
  exercising simultaneous read and write on the same file
Open a temporary write descriptor in Setattr when the kernel sends a
size change without a file handle (the truncate(2) syscall, as opposed
to ftruncate(fd) which passes the handle). Previously this returned
ENOTSUP.

- fuse/writable: open, truncate, flush, close in Setattr else branch
- fuse/fusetest: add TruncatePath to the shared writable suite
- test/cli/fuse: add end-to-end truncation test covering ftruncate(fd),
  syscall.Truncate(path), and open(O_TRUNC) through a real daemon
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants