| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
test: fix flakes that force CI re-runs (#11413) * fix(routing): keep peers found before the timeout The DHT returns the closest peers it reached together with the context error when a lookup runs past its deadline. We dropped both, so any lookup slower than the routing server's per-request timeout came back as HTTP 500 with nothing in it, indistinguishable from a lookup that found no peers at all. Return what we have, and only error when the set is empty. * test: use local dht swarm for routing v1 test GetClosestPeers joined the public Amino DHT with real bootstrap peers, so the assertions depended on a CI runner reaching bootstrap.libp2p.io from a cold repo. When it could not, the test retried for five minutes and failed; ten such failures since v0.42.0, every one green on re-run. Bootstrap from the harness's in-process DHT peers instead, which the provider tests already use and this one predates. The window drops from five minutes to sixty seconds because there is no longer anything slow to wait for, and passing runs go from tens of seconds to under one. * test: stop handing out ports the kernel reuses NewRandPort binds port zero, notes the number, closes the socket and hands the number to the caller, which leaves a window for anything else on the machine to take it. The number also came from the ephemeral range, the same pool every outgoing connection draws from, and the CLI suite opens a lot of those. Both TestP2PForeground tunnel subtests died on "bind: address already in use" for a server the test binds itself. - NewTCPListener hands back the bound listener, closing that window for callers that listen in-process - ports for daemons we spawn now come from below the ephemeral range, so an outgoing connection cannot land on one * test: sync gc tests to the adder, not the clock TestAddGCLive asserted that gc had not started yet, but the only thing it waited for was the first file's output event. Between that event and the adder reaching the next file there is a gap, and the adder hands the pin lock to a waiting gc at exactly that boundary, so on a loaded runner gc really had started and the assertion was right to fail. Wrap the pipe so the test learns when the adder is inside the hanging file, and poll GCRequested instead of sleeping 100ms to know gc is queued. TestAddMultipleGCLive gets the same treatment for its two sleeps: too short there means gc never gets the lock and the test waits out its five second timeout instead. * test: move watched file in atomically os.WriteFile creates the file and fills it in two steps, and ipfswatch adds whatever is on disk when the create event wakes it. Catch it between the two and it adds an empty file, so the CID the test pulls out of the log reads back as nothing. Stage the file outside the watched directory and rename it in, which the watcher sees as one event for a file that is already complete. * test(sharness): poll the daemon request log The test backgrounded "ipfs log tail", slept 100ms and expected the daemon to be listing the request. The daemon only sees it once the client has started up and connected, which on a loaded runner takes longer than that, and then both the active and the inactive assertion fail together because the entry never appears at all. Poll for each state instead. The extra requests that polling makes push the daemon closer to the point where it drops finished entries from the log, so keep them with "diag cmds set-time" first. * test(sharness): drop stale peer count check The connect case opened by re-asserting that the previous case had left zero peers connected. Disconnecting is not permanent: the DHT keeps the other node in its routing table and re-dials it on any refresh, so that count is only true for as long as nothing else runs. What this case is named for, connecting with a bare /p2p/ address, is still covered by the connect itself and the peer count after it. * test(fuse): mount one node at a time Every parallel subtest does identical setup before mounting, so they all reach the mount together and around twenty setuid fusermount helpers open /dev/fuse inside the same instant. One occasionally comes back with a bare exit status 1. Take a lock for the mount call itself, which the subtests only hold for tens of milliseconds. Also report the failure instead of panicking: a panic failed all 37 tests in the package and left daemons behind, and the daemon's stderr, where fusermount says what actually went wrong, was captured and then thrown away. * test: compare cat output byte for byte The payload is 100 random bytes and the comparison ran through Trimmed(), which strips one trailing newline. Roughly one run in 256 ends in 0x0a and loses it. * test: wait for the fast-provide log line The daemon writes the line before it answers the RPC, but the test reads a buffer that a goroutine fills by copying the daemon's stderr, and that copy can still be behind when the command returns. Wait for the line rather than assuming it has landed. * test: allow for ipns republish mid-test A minute after the daemon starts, the republisher re-signs every key and publishes it again, giving the same value a new signature and expiry. The test captured one PUT body and compared it byte for byte with what routing returned, so a run slow enough to straddle that minute compared the first record against the second. Keep every record the mock is sent and require that routing's answer is one of them, which is what the assertion was reaching for. * fix(examples): turn off mdns in library example The example connects its two nodes by address, but left mDNS on, so local discovery could connect them first. A connection opened while a node is still being built is invisible to that node's bitswap, which only learns about connections made after it registers its notifier, and with no routing configured there is nothing to fall back on. The final fetch then waited forever and the test died on its two minute timeout with no clue why. Turning mDNS off makes the explicit dial the only way the two can meet, and keeps the example off the reader's LAN. Alongside that: - connectToPeers returns dial errors instead of logging and continuing into a fetch that cannot succeed - the example's own deadline now fits inside the test budget, so a stall names the step that hung - CommandContext so a hung child does not outlive the test * ci: make helia-interop job resilient Seven failures since v0.42.0 came from this job's setup rather than from any incompatibility. It installs whatever @helia/interop published last, and upstream shipped three packages in a row whose test config does not work from inside node_modules; a GitHub blip took out the rest. - find the compiled specs and pass them to aegir, instead of patching the config upstream ships into node_modules and grepping its text - pin node to a major: setup-node resolves an lts/ alias through a GitHub manifest with no retry and no fallback, and newer node rejects a flag aegir sets unconditionally - retry the registry lookup and fail loudly, since the old one-liner could not fail and left an empty cache key behind - install the exact version the cache key names, and only save the cache once the install is known good - drop the playwright apt packages, unused since this job stopped running browser targets | 2 个月前 | |
feat: add built-in `ipfs update` command (#11203) * feat: add built-in `ipfs update` command adds `ipfs update` command tree that downloads pre-built Kubo binaries from GitHub Releases, verifies SHA-512 checksums, and replaces the running binary in place. subcommands: - `ipfs update check` -- query GitHub for newer versions - `ipfs update versions` -- list available releases - `ipfs update install [version]` -- download, verify, backup, and atomically replace the current binary - `ipfs update revert` -- restore the previously backed up binary from `$IPFS_PATH/old-bin/` read-only subcommands (check, versions) work while the daemon is running. install and revert require the daemon to be stopped first. design decisions: - uses GitHub Releases API instead of dist.ipfs.tech because GitHub is harder to censor in regions that block IPFS infrastructure - honors GITHUB_TOKEN/GH_TOKEN to avoid unauthenticated rate limits - backs up the current binary before replacing, with permission-error fallback that saves to a temp dir with manual `sudo mv` instructions - `KUBO_UPDATE_GITHUB_URL` env var redirects API calls for integration testing; `IPFS_VERSION_FAKE` overrides the reported version - unit tests use mock HTTP servers and the var override; CLI tests use the env vars with a temp binary copy so the real build is never touched resolves https://github.com/ipfs/kubo/issues/10937 * fix(update): harden download and extraction - cap decompressed binary at 1 GB to block zip/tar bombs - propagate tar.gz/zip errors instead of swallowing them - fall back to 1h context timeout when --timeout is not set - warn on stderr when daemon lock check fails - clarify that fetch+verify+extract complete before touching binary * fix(update): resolve binary path on windows The test harness hardcodes the binary path as `cmd/ipfs/ipfs` without the `.exe` suffix. On Windows the built binary is `ipfs.exe`, so copyBuiltBinary needs to append the extension. * fix(update): handle windows binary locking in install test On Windows the OS locks the running executable, so atomicfile cannot rename over it. The install command falls back to saving the new binary to a temp path. Accept both outcomes in TestUpdateInstall: in-place replacement (Unix) or permission-denied fallback (Windows). Also fix stash path to include .exe suffix on Windows. - test/cli/update_test.go: branch on runtime.GOOS for install assertions - test/sharness/t0063-external.sh: remove, tested the old ExternalBinary delegation which is replaced by the built-in update command - .github/workflows/test-migrations.yml: pass GITHUB_TOKEN to avoid rate limits * fix(test): handle windows EINVAL on process signal after wait On Windows, Process.Wait() sets the handle state to "released" rather than "done", so a subsequent Signal() returns syscall.EINVAL instead of os.ErrProcessDone. This caused StopDaemon cleanup to panic on Windows CI. Treat both errors as "process already exited". * feat(update): add 'clean' subcommand Drops every backed-up Kubo binary from $IPFS_PATH/old-bin/ so users can reclaim disk space without hand-deleting files. Safe with the daemon running, only touches the backup directory. - update.go: extract stashDirName const, factor out listStashes() helper, add updateCleanCmd - commands_test.go: register /update/clean - test/cli/update_test.go: TestUpdateClean covers removal, empty dir, json output, and preservation of unrelated files * docs(changelog): tighten ipfs update entry Drop the marketing opener, the duplicate install example, and the revert/versions sentence; all are covered by 'ipfs update --help'. Mention the new 'clean' subcommand in the trailing pointer. * fix(test): skip fuse cli tests on non-unix Both fuse_test.go and realworld_test.go rely on Unix-only APIs (syscall.Truncate, POSIX tools). The sibling xattr_*_test.go files were already gated, but these two compiled everywhere, so any workflow running 'go test ./test/cli/...' on Windows hit 'undefined: syscall.Truncate'. Use the same '(linux || darwin || freebsd) && !nofuse' constraint that the fuse/ packages already use so platform gating is consistent. * fix(test): run install/revert sequentially TestUpdateInstall and TestUpdateRevert write a copy of the ipfs binary and then exec it. When other tests run in parallel, a concurrent fork() can inherit the still-open write fd into its child, leaving the freshly written file 'text file busy' for exec until the sibling child execs. Dropping t.Parallel() on these two tests ensures no other goroutine is mid-fork while the binary is being written, which is the only reliable way to avoid the ETXTBSY race without clever fd tricks. * ci(update): use cloudflare/google DNS on macos GitHub's macOS runners intermittently lose DNS for api.github.com, which fails the real-network subtests in TestUpdate. Point the resolver at 1.1.1.1 and 8.8.8.8 on every active network service and flush the DNS cache before running the update tests. * fix(update): fsync before close in atomicfile and stash * fix(update): use unique temp file in permission fallback The previous fallback wrote to a predictable path (/tmp/ipfs-<ver>), which on shared systems lets a local attacker pre-create the path as a symlink and steer the user's subsequent 'sudo mv' anywhere. Switch to os.CreateTemp so the path is unique and exclusively owned by this process. * refactor(update): rename test env vars to TEST_KUBO_* IPFS_VERSION_FAKE and KUBO_UPDATE_GITHUB_URL are test-only escape hatches with no production use case. The TEST_ prefix signals this clearly and reduces the chance of accidental use in production. - IPFS_VERSION_FAKE -> TEST_KUBO_VERSION - KUBO_UPDATE_GITHUB_URL -> TEST_KUBO_UPDATE_GITHUB_URL * style(update): unshadow err in stashBinary * fix(update): warn when IPFS path can't be resolved silently skipping the daemon lock check on path-resolution failure can mask a misconfigured IPFS_PATH; print a warning so the user notices before the install proceeds. * fix(update): revert atomicfile Sync, use errors.Is for EOF Revert the Sync() addition in atomicfile.Close() to avoid widening the failure surface for existing migration callers that panic on Close errors (Must(out.Close()) in WithBackup). The stashBinary fsync in update.go is kept since that code path is new. - revert repo/fsrepo/migrations/atomicfile/atomicfile.go to master - use errors.Is(err, io.EOF) in extractFromTarGz | 6 个月前 | |
fix(fuse): switch to hanwen/go-fuse (#11272) * test(fuse): consolidate FUSE tests into test/cli/fuse 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) * docs: document FUSE test split between unit and e2e 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. * ci: prevent stale FUSE mounts from failing fuse-tests 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 * ci: only symlink fusermount3 when fusermount is missing * fix(fuse): remove goroutine leak in IPNS Flush handler 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. * fix(fuse): add mutex to IPNS file handle operations 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. * refactor(fuse): remove dead File.Forget method 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. * fix(fuse): flush IPNS directory after Remove and Rename 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. * fix(fuse): inherit CID builder and flush on IPNS Create 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. * test(fuse): add IPNS Remove and non-empty rmdir tests 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. * feat(fuse): read UnixFS mode/mtime, add StoreMtime/StoreMode config 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 * feat(fuse): add ipfs.cid xattr to all mounts 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.*). * feat(fuse): switch from bazil.org/fuse to hanwen/go-fuse v2 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 * fix(fuse): close fd on error in Open to prevent leak MFS enforces a single-writer lock, so a leaked write descriptor blocks all subsequent opens of that file until GC. * fix(fuse): detect external unmount via server.Wait Without this, IsActive stays true after `fusermount -u` and Unmount returns nil instead of ErrNotMounted. * fix(fuse): return actual error from Unlink/Rmdir, not ENOENT After confirming the child exists, an Unlink failure could be an IO error. Returning ENOENT would hide the real cause. * fix(fuse): reuse DagReader per open, pass ctx to all reads 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. * chore(fuse): cleanup dead code, add var comments - remove dead `_ = mntDir` in TestXattrCID - comment why immutableAttrCacheTime and mutableCacheTime are var - add TODO for using IPNS record TTL as cache timeout * chore(fuse): replace OSXFUSE 2.x check with macFUSE detection 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. * fix(fuse): include mountpoint path in mount errors 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. * chore(ci): remove bazil fusermount workaround go-fuse finds fusermount3 natively, no symlink needed. The stale mount cleanup was for bazil's fstestutil which we no longer use. * docs: update v0.41 changelog for FUSE rewrite * chore(deps): bump boxo for full FileDescriptor serialization boxo@64be0815 extends the mutex from Flush/Close to all FileDescriptor operations (Read, Write, Seek, Truncate, Size), preventing data races on the underlying DagModifier. * chore(deps): bump boxo to merged ipfs/boxo#1133 Picks up full FileDescriptor serialization: the mutex now covers all operations (Read, Write, Seek, Truncate, Size), not just Flush and Close. * feat(fuse): CAP_ATOMIC_O_TRUNC, new integration tests Advertise CAP_ATOMIC_O_TRUNC so the kernel sends O_TRUNC inside Open instead of doing a separate SETATTR(size=0) first. Without this, the kernel's SETATTR needs to open a write descriptor inside Setattr, which deadlocks on MFS's single-writer lock. Move kernel cache invalidation from Flush to Release because mfsFD.Close (in Release) is where the final DAG node is committed. Upgrade go-fuse to latest for ExtraCapabilities support. New tests for both MFS and IPNS: - TestOpenTrunc, TestSeekAndWrite, TestOverwriteExisting - TestTempFileRename, TestVimSavePattern, TestRsyncPattern (skipped pending rename-over-existing and cache fixes) * fix(fuse): rename-over-existing, bump boxo for flushUp race fix 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. * fix(fuse): unskip VimSavePattern, bump boxo for setNodeData fix 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. * fix(fuse): build tags for cross-compilation 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. * fix(test): use fusermount3 in CLI FUSE tests 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. * feat(fuse): symlink support on writable mounts 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). * fix(fuse): checked type assertion in MFS Rename 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. * fix(test): add missing continue in stress test 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. * fix(fuse): return error from Readdir when DAG.Get fails 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. * docs: remove duplicate fsync bullet in changelog * ci: clean up stale FUSE mounts in fuse-tests job 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. * chore(deps): bump boxo to merged ipfs/boxo#1134 Picks up flushUp unlinked-entry guard and setNodeData content link preservation. * docs: add build tag comments, normalize tag style 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. * fix(fuse): add Setattr to directories for chmod and mtime 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. * docs: clarify directory support and spec link for StoreMtime/StoreMode - 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 * fix(fuse): use proper mode conversion, document 9-bit limit 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. * feat(fuse): symlink Setattr with mtime persistence 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. * fix(fuse): return EIO instead of panicking on unknown node type Replace panic with log.Errorf + syscall.EIO in IPNS Directory.Lookup for unexpected MFS node types. Also remove duplicate comment block on File.Flush. * docs: update FUSE docs for go-fuse migration - 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 * refactor(fuse): extract shared writable types and test suite 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 * feat(fuse): add macOS-specific mount options 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 * fix(fuse): detect symlinks in readdir, fix stale refs 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 * fix(fuse): normalize deprecated ipfs_cid xattr to ipfs.cid 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. * fix(fuse): serialize concurrent reads on readonly file handles 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) * fix(fuse): bypass MFS locking for read-only opens 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 * fix(fuse): support truncate(path, size) without open fd 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 * ci(fuse): get stack traces on test hangs The fuse-tests job was being silently cancelled by GitHub at 10min because Go's per-test timeout (5m) was the same order as the job timeout, and GOTRACEBACK=single hid the hung goroutines anyway. - shrink TEST_FUSE_TIMEOUT to 4m so Go's panic fires first - shrink job timeout-minutes to 6 (normal run is ~3min) - set GOTRACEBACK=all so the panic dumps every goroutine, not just the timer * fix(fuse): fill attrs in FileInode.Setattr response Without this, the kernel could cache zero attrs after a chmod, touch, or ftruncate until AttrTimeout (1s) expired. Dir.Setattr and Symlink.Setattr already fill out.Attr; FileInode.Setattr now matches. * docs(config): clarify Mounts.IPNS writability scope Only directories backed by keys the node holds are writable. All other names resolve via IPNS to read-only symlinks into the /ipfs mount. * fuse: review cleanup for go-fuse migration Final pass on #11272 addressing review feedback. - writable: panic in NewDir if Config.DAG is nil. Both call sites already supply it, but a nil value silently fell back to the MFS path in FileInode.Open, re-introducing the rsync --inplace deadlock the read-only fast path was added to fix. - writable: document Dir.Rename non-atomicity. Source unlink happens before destination add, so any failure between the two loses the source. An atomic fix requires changes in boxo/mfs. - writable: add unit test locking in that Symlink.Setattr accepts a mode-only request without erroring and does not store the requested mode (POSIX symlinks have no meaningful permission bits). - docs/config: correct StoreMode default modes; the previous text listed 0666 for files, which the code never uses. * docs(config): list StoreMtime and StoreMode in Mounts TOC * fix(fuse): fill EntryOut attrs in Dir.Create and Dir.Mkdir Without this, fstat on the file handle returned by Create reports mode 0 and size 0 for up to AttrTimeout (1s), because the kernel caches the empty attrs from the Create response. Path-based stat goes through Lookup which already fills attrs, so the bug only shows up via fstat. Mirrors the same fix already applied to FileInode.Setattr. Dir.Mkdir gets the same fillAttr treatment for consistency, plus a TODO noting that boxo's mfs.Directory.Mkdir accepts no mode arg so the caller's mode is dropped on creation. Adds CreateAttrsImmediate and MkdirAttrsImmediate to the shared writable suite to guard both paths against future regressions. * fix(fuse): map context cancellation to EINTR in read paths When a userspace process is killed mid-read (Ctrl-C, SIGKILL on a stuck cat) the kernel sends FUSE_INTERRUPT and go-fuse cancels the per-request context. fs.ToErrno does not recognise context.Canceled and falls through to "function not implemented", which the kernel cannot act on. Map context.Canceled and DeadlineExceeded to EINTR so the syscall is correctly aborted. - mount/errno.go: new ReadErrno helper used by all context-aware read paths in both readonly and writable mounts - readonly: applied to Node.Open, Node.Readdir, roFileHandle.Read - writable: applied to FileInode.Open, FileHandle.Read, roFileHandle.Read - readonly/ipfs_test.go: TestReadCancellationUnblocks guards the contract via a blocking DagReader fake; without ReadErrno the test reports "function not implemented" instead of EINTR * test(fuse): add OExcl, DirRename, SparseWrite, FsyncCrossHandle Coverage gaps in the shared writable suite: - OExcl: lock files and atomic-create patterns rely on the second open with O_CREATE|O_EXCL failing with EEXIST - DirRename: previously only file rename and cross-dir file rename were tested; this exercises Rename on a directory inode - SparseWrite: WriteAt past the end of an empty file must report the correct size and return zeros for the gap - FsyncCrossHandle: a reader on a fresh fd must see data flushed by fsync on the writer fd, not just after close * test(fuse): cover external unmount on /ipns and /mfs Previously TestExternalUnmount only exercised /ipfs, leaving the goroutine that watches fuse.Server.Wait() untested for the other two mounts. Refactor into a table-driven test that runs the same fusermount/umount-then-IsActive flow against all three mounts. Switch to coremock.NewMockNode so the node is online: doMount only attaches the /ipns mount when node.IsOnline is true, and the table needs all three populated. * fix(commands): align 'ipfs mount' output columns MountCmd's LongDescription has "MFS mounted at:" with two spaces so the column lines up with the 4-char "IPFS" and "IPNS" rows above, but the runtime encoder and the daemon's startup print used a single space and produced misaligned output. Bring both runtime sites in line with the help text, and update the two existing test fixtures (test/cli/fuse and the sharness test-lib helper that t0040-add-and-cat.sh still uses) to expect the aligned form. * fix(fuse): invalidate kernel cache on Fsync FileHandle.Fsync only flushed the MFS file descriptor and left the kernel's cached attrs and content for the inode untouched. A fresh reader on the same path then saw the size cached from the original Create response (zero), reading zero bytes regardless of how much the writer had synced. Mirror the cache invalidation already done in Flush via inode.NotifyContent(0, 0) so a writer that fsyncs while another process opens the file (vim then a follow-up cat, IDE then a language server) sees consistent state. Sharpen the FsyncCrossHandle assertion to report the size delta on failure; the bug surfaced as got=0/want=500 only after switching from bytes.Equal to require.Equal. * chore(gitignore): ignore test_fuse_unit and test_fuse_cli json output The new test_fuse_unit and test_fuse_cli make targets emit test/fuse/fuse-unit-tests.json and test/fuse/fuse-cli-tests.json respectively, the same gotestsum --jsonfile pattern that test_unit and test_cli already use. Add them to the same .gitignore section so a local test run does not leave the working tree dirty. * test(fuse): end-to-end coverage with real POSIX tools Adds TestFUSERealWorld in test/cli/fuse/realworld_test.go: a single shared-daemon test with 18 subtests that exercise the writable /mfs mount through the actual binaries users invoke (sh, cat, seq, wc, ls, stat, cp, mv, rm, ln, readlink, find, dd, sha256sum, tar, rsync, vim). Each subtest verifies the result both via the FUSE filesystem and via 'ipfs files read|stat|ls' so both views agree. Synthetic payloads default to 1 MiB + 1 byte so multi-chunk read/write paths are exercised, not just single-chunk fast paths. External tools are required, not optional: a missing binary fails the test loudly so a CI image change cannot silently turn the suite green. The whole-suite TEST_FUSE gate is the only place a developer is allowed to skip. runCmd forces LC_ALL=C so locale-sensitive tool output (date formats in 'ls -l', decimal separators in 'wc', localized error messages, find/ls collation) is deterministic regardless of the runner's locale settings. One shared daemon across all 18 subtests keeps total runtime under two seconds; isolation comes from per-subtest subdirectories under the mount. | 6 个月前 | |
fix(fuse): switch to hanwen/go-fuse (#11272) * test(fuse): consolidate FUSE tests into test/cli/fuse 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) * docs: document FUSE test split between unit and e2e 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. * ci: prevent stale FUSE mounts from failing fuse-tests 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 * ci: only symlink fusermount3 when fusermount is missing * fix(fuse): remove goroutine leak in IPNS Flush handler 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. * fix(fuse): add mutex to IPNS file handle operations 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. * refactor(fuse): remove dead File.Forget method 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. * fix(fuse): flush IPNS directory after Remove and Rename 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. * fix(fuse): inherit CID builder and flush on IPNS Create 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. * test(fuse): add IPNS Remove and non-empty rmdir tests 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. * feat(fuse): read UnixFS mode/mtime, add StoreMtime/StoreMode config 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 * feat(fuse): add ipfs.cid xattr to all mounts 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.*). * feat(fuse): switch from bazil.org/fuse to hanwen/go-fuse v2 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 * fix(fuse): close fd on error in Open to prevent leak MFS enforces a single-writer lock, so a leaked write descriptor blocks all subsequent opens of that file until GC. * fix(fuse): detect external unmount via server.Wait Without this, IsActive stays true after `fusermount -u` and Unmount returns nil instead of ErrNotMounted. * fix(fuse): return actual error from Unlink/Rmdir, not ENOENT After confirming the child exists, an Unlink failure could be an IO error. Returning ENOENT would hide the real cause. * fix(fuse): reuse DagReader per open, pass ctx to all reads 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. * chore(fuse): cleanup dead code, add var comments - remove dead `_ = mntDir` in TestXattrCID - comment why immutableAttrCacheTime and mutableCacheTime are var - add TODO for using IPNS record TTL as cache timeout * chore(fuse): replace OSXFUSE 2.x check with macFUSE detection 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. * fix(fuse): include mountpoint path in mount errors 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. * chore(ci): remove bazil fusermount workaround go-fuse finds fusermount3 natively, no symlink needed. The stale mount cleanup was for bazil's fstestutil which we no longer use. * docs: update v0.41 changelog for FUSE rewrite * chore(deps): bump boxo for full FileDescriptor serialization boxo@64be0815 extends the mutex from Flush/Close to all FileDescriptor operations (Read, Write, Seek, Truncate, Size), preventing data races on the underlying DagModifier. * chore(deps): bump boxo to merged ipfs/boxo#1133 Picks up full FileDescriptor serialization: the mutex now covers all operations (Read, Write, Seek, Truncate, Size), not just Flush and Close. * feat(fuse): CAP_ATOMIC_O_TRUNC, new integration tests Advertise CAP_ATOMIC_O_TRUNC so the kernel sends O_TRUNC inside Open instead of doing a separate SETATTR(size=0) first. Without this, the kernel's SETATTR needs to open a write descriptor inside Setattr, which deadlocks on MFS's single-writer lock. Move kernel cache invalidation from Flush to Release because mfsFD.Close (in Release) is where the final DAG node is committed. Upgrade go-fuse to latest for ExtraCapabilities support. New tests for both MFS and IPNS: - TestOpenTrunc, TestSeekAndWrite, TestOverwriteExisting - TestTempFileRename, TestVimSavePattern, TestRsyncPattern (skipped pending rename-over-existing and cache fixes) * fix(fuse): rename-over-existing, bump boxo for flushUp race fix 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. * fix(fuse): unskip VimSavePattern, bump boxo for setNodeData fix 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. * fix(fuse): build tags for cross-compilation 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. * fix(test): use fusermount3 in CLI FUSE tests 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. * feat(fuse): symlink support on writable mounts 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). * fix(fuse): checked type assertion in MFS Rename 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. * fix(test): add missing continue in stress test 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. * fix(fuse): return error from Readdir when DAG.Get fails 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. * docs: remove duplicate fsync bullet in changelog * ci: clean up stale FUSE mounts in fuse-tests job 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. * chore(deps): bump boxo to merged ipfs/boxo#1134 Picks up flushUp unlinked-entry guard and setNodeData content link preservation. * docs: add build tag comments, normalize tag style 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. * fix(fuse): add Setattr to directories for chmod and mtime 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. * docs: clarify directory support and spec link for StoreMtime/StoreMode - 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 * fix(fuse): use proper mode conversion, document 9-bit limit 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. * feat(fuse): symlink Setattr with mtime persistence 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. * fix(fuse): return EIO instead of panicking on unknown node type Replace panic with log.Errorf + syscall.EIO in IPNS Directory.Lookup for unexpected MFS node types. Also remove duplicate comment block on File.Flush. * docs: update FUSE docs for go-fuse migration - 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 * refactor(fuse): extract shared writable types and test suite 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 * feat(fuse): add macOS-specific mount options 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 * fix(fuse): detect symlinks in readdir, fix stale refs 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 * fix(fuse): normalize deprecated ipfs_cid xattr to ipfs.cid 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. * fix(fuse): serialize concurrent reads on readonly file handles 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) * fix(fuse): bypass MFS locking for read-only opens 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 * fix(fuse): support truncate(path, size) without open fd 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 * ci(fuse): get stack traces on test hangs The fuse-tests job was being silently cancelled by GitHub at 10min because Go's per-test timeout (5m) was the same order as the job timeout, and GOTRACEBACK=single hid the hung goroutines anyway. - shrink TEST_FUSE_TIMEOUT to 4m so Go's panic fires first - shrink job timeout-minutes to 6 (normal run is ~3min) - set GOTRACEBACK=all so the panic dumps every goroutine, not just the timer * fix(fuse): fill attrs in FileInode.Setattr response Without this, the kernel could cache zero attrs after a chmod, touch, or ftruncate until AttrTimeout (1s) expired. Dir.Setattr and Symlink.Setattr already fill out.Attr; FileInode.Setattr now matches. * docs(config): clarify Mounts.IPNS writability scope Only directories backed by keys the node holds are writable. All other names resolve via IPNS to read-only symlinks into the /ipfs mount. * fuse: review cleanup for go-fuse migration Final pass on #11272 addressing review feedback. - writable: panic in NewDir if Config.DAG is nil. Both call sites already supply it, but a nil value silently fell back to the MFS path in FileInode.Open, re-introducing the rsync --inplace deadlock the read-only fast path was added to fix. - writable: document Dir.Rename non-atomicity. Source unlink happens before destination add, so any failure between the two loses the source. An atomic fix requires changes in boxo/mfs. - writable: add unit test locking in that Symlink.Setattr accepts a mode-only request without erroring and does not store the requested mode (POSIX symlinks have no meaningful permission bits). - docs/config: correct StoreMode default modes; the previous text listed 0666 for files, which the code never uses. * docs(config): list StoreMtime and StoreMode in Mounts TOC * fix(fuse): fill EntryOut attrs in Dir.Create and Dir.Mkdir Without this, fstat on the file handle returned by Create reports mode 0 and size 0 for up to AttrTimeout (1s), because the kernel caches the empty attrs from the Create response. Path-based stat goes through Lookup which already fills attrs, so the bug only shows up via fstat. Mirrors the same fix already applied to FileInode.Setattr. Dir.Mkdir gets the same fillAttr treatment for consistency, plus a TODO noting that boxo's mfs.Directory.Mkdir accepts no mode arg so the caller's mode is dropped on creation. Adds CreateAttrsImmediate and MkdirAttrsImmediate to the shared writable suite to guard both paths against future regressions. * fix(fuse): map context cancellation to EINTR in read paths When a userspace process is killed mid-read (Ctrl-C, SIGKILL on a stuck cat) the kernel sends FUSE_INTERRUPT and go-fuse cancels the per-request context. fs.ToErrno does not recognise context.Canceled and falls through to "function not implemented", which the kernel cannot act on. Map context.Canceled and DeadlineExceeded to EINTR so the syscall is correctly aborted. - mount/errno.go: new ReadErrno helper used by all context-aware read paths in both readonly and writable mounts - readonly: applied to Node.Open, Node.Readdir, roFileHandle.Read - writable: applied to FileInode.Open, FileHandle.Read, roFileHandle.Read - readonly/ipfs_test.go: TestReadCancellationUnblocks guards the contract via a blocking DagReader fake; without ReadErrno the test reports "function not implemented" instead of EINTR * test(fuse): add OExcl, DirRename, SparseWrite, FsyncCrossHandle Coverage gaps in the shared writable suite: - OExcl: lock files and atomic-create patterns rely on the second open with O_CREATE|O_EXCL failing with EEXIST - DirRename: previously only file rename and cross-dir file rename were tested; this exercises Rename on a directory inode - SparseWrite: WriteAt past the end of an empty file must report the correct size and return zeros for the gap - FsyncCrossHandle: a reader on a fresh fd must see data flushed by fsync on the writer fd, not just after close * test(fuse): cover external unmount on /ipns and /mfs Previously TestExternalUnmount only exercised /ipfs, leaving the goroutine that watches fuse.Server.Wait() untested for the other two mounts. Refactor into a table-driven test that runs the same fusermount/umount-then-IsActive flow against all three mounts. Switch to coremock.NewMockNode so the node is online: doMount only attaches the /ipns mount when node.IsOnline is true, and the table needs all three populated. * fix(commands): align 'ipfs mount' output columns MountCmd's LongDescription has "MFS mounted at:" with two spaces so the column lines up with the 4-char "IPFS" and "IPNS" rows above, but the runtime encoder and the daemon's startup print used a single space and produced misaligned output. Bring both runtime sites in line with the help text, and update the two existing test fixtures (test/cli/fuse and the sharness test-lib helper that t0040-add-and-cat.sh still uses) to expect the aligned form. * fix(fuse): invalidate kernel cache on Fsync FileHandle.Fsync only flushed the MFS file descriptor and left the kernel's cached attrs and content for the inode untouched. A fresh reader on the same path then saw the size cached from the original Create response (zero), reading zero bytes regardless of how much the writer had synced. Mirror the cache invalidation already done in Flush via inode.NotifyContent(0, 0) so a writer that fsyncs while another process opens the file (vim then a follow-up cat, IDE then a language server) sees consistent state. Sharpen the FsyncCrossHandle assertion to report the size delta on failure; the bug surfaced as got=0/want=500 only after switching from bytes.Equal to require.Equal. * chore(gitignore): ignore test_fuse_unit and test_fuse_cli json output The new test_fuse_unit and test_fuse_cli make targets emit test/fuse/fuse-unit-tests.json and test/fuse/fuse-cli-tests.json respectively, the same gotestsum --jsonfile pattern that test_unit and test_cli already use. Add them to the same .gitignore section so a local test run does not leave the working tree dirty. * test(fuse): end-to-end coverage with real POSIX tools Adds TestFUSERealWorld in test/cli/fuse/realworld_test.go: a single shared-daemon test with 18 subtests that exercise the writable /mfs mount through the actual binaries users invoke (sh, cat, seq, wc, ls, stat, cp, mv, rm, ln, readlink, find, dd, sha256sum, tar, rsync, vim). Each subtest verifies the result both via the FUSE filesystem and via 'ipfs files read|stat|ls' so both views agree. Synthetic payloads default to 1 MiB + 1 byte so multi-chunk read/write paths are exercised, not just single-chunk fast paths. External tools are required, not optional: a missing binary fails the test loudly so a CI image change cannot silently turn the suite green. The whole-suite TEST_FUSE gate is the only place a developer is allowed to skip. runCmd forces LC_ALL=C so locale-sensitive tool output (date formats in 'ls -l', decimal separators in 'wc', localized error messages, find/ls collation) is deterministic regardless of the runner's locale settings. One shared daemon across all 18 subtests keeps total runtime under two seconds; isolation comes from per-subtest subdirectories under the mount. | 6 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 2 个月前 | ||
| 6 个月前 | ||
| 6 个月前 | ||
| 6 个月前 |