From efa1bfaf68de627b905b3f606dca1e442a54f894 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 05:45:15 +0000 Subject: [PATCH] Bump github.com/hanwen/go-fuse/v2 from 2.10.1 to 2.11.0 Bumps [github.com/hanwen/go-fuse/v2](https://github.com/hanwen/go-fuse) from 2.10.1 to 2.11.0. - [Commits](https://github.com/hanwen/go-fuse/compare/v2.10.1...v2.11.0) --- updated-dependencies: - dependency-name: github.com/hanwen/go-fuse/v2 dependency-version: 2.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/hanwen/go-fuse/v2/AUTHORS | 1 + vendor/github.com/hanwen/go-fuse/v2/fs/api.go | 3 +- .../github.com/hanwen/go-fuse/v2/fs/bridge.go | 2 +- .../hanwen/go-fuse/v2/fs/bridge_linux.go | 4 +- .../hanwen/go-fuse/v2/fs/dirstream.go | 8 +- .../github.com/hanwen/go-fuse/v2/fs/files.go | 280 ++++++++++-------- .../hanwen/go-fuse/v2/fs/files_darwin.go | 5 +- .../hanwen/go-fuse/v2/fs/files_linux.go | 20 +- .../hanwen/go-fuse/v2/fs/files_unix.go | 5 +- .../github.com/hanwen/go-fuse/v2/fs/inode.go | 5 +- .../hanwen/go-fuse/v2/fs/loopback.go | 49 ++- .../hanwen/go-fuse/v2/fs/loopback_linux.go | 1 - vendor/github.com/hanwen/go-fuse/v2/fs/mem.go | 27 +- .../github.com/hanwen/go-fuse/v2/fuse/api.go | 35 ++- .../hanwen/go-fuse/v2/fuse/bufferpool.go | 7 +- .../hanwen/go-fuse/v2/fuse/constants.go | 2 +- .../hanwen/go-fuse/v2/fuse/context.go | 2 +- .../hanwen/go-fuse/v2/fuse/direntry.go | 9 +- .../hanwen/go-fuse/v2/fuse/fusefd.go | 241 +++++++++++++++ .../fuse/{server_linux.go => fusefd_linux.go} | 12 +- .../fuse/{server_unix.go => fusefd_other.go} | 9 +- .../github.com/hanwen/go-fuse/v2/fuse/misc.go | 21 +- .../hanwen/go-fuse/v2/fuse/mount_linux.go | 10 +- .../hanwen/go-fuse/v2/fuse/opcode.go | 63 ++-- .../hanwen/go-fuse/v2/fuse/opcode_darwin.go | 26 ++ .../go-fuse/v2/fuse/passthrough_linux.go | 45 ++- .../hanwen/go-fuse/v2/fuse/print.go | 17 +- .../hanwen/go-fuse/v2/fuse/protocol-server.go | 33 ++- .../github.com/hanwen/go-fuse/v2/fuse/read.go | 5 +- .../hanwen/go-fuse/v2/fuse/request.go | 7 +- .../hanwen/go-fuse/v2/fuse/server.go | 276 ++++++----------- .../hanwen/go-fuse/v2/fuse/splice_linux.go | 12 +- .../hanwen/go-fuse/v2/fuse/syscall_unix.go | 6 +- .../hanwen/go-fuse/v2/fuse/types.go | 2 + .../hanwen/go-fuse/v2/fuse/types_darwin.go | 6 + .../hanwen/go-fuse/v2/internal/access.go | 61 ++-- .../hanwen/go-fuse/v2/internal/ioctl/ioctl.go | 13 +- .../hanwen/go-fuse/v2/splice/splice.go | 2 +- vendor/modules.txt | 2 +- 41 files changed, 827 insertions(+), 513 deletions(-) create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd.go rename vendor/github.com/hanwen/go-fuse/v2/fuse/{server_linux.go => fusefd_linux.go} (65%) rename vendor/github.com/hanwen/go-fuse/v2/fuse/{server_unix.go => fusefd_other.go} (70%) create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/opcode_darwin.go diff --git a/go.mod b/go.mod index 217bbe14..30ac9913 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.0 require ( github.com/andybalholm/brotli v1.2.1 - github.com/hanwen/go-fuse/v2 v2.10.1 + github.com/hanwen/go-fuse/v2 v2.11.0 github.com/prometheus/client_golang v1.23.2 golang.org/x/crypto v0.53.0 golang.org/x/net v0.56.0 diff --git a/go.sum b/go.sum index 5926b413..b77ce30b 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hanwen/go-fuse/v2 v2.10.1 h1:QAqZuc9+aBtTou+OPruU/hkYQYCkgPtQd2QaepHkTTs= -github.com/hanwen/go-fuse/v2 v2.10.1/go.mod h1:aU7NkGYZUmuJrZapoI3mEcNve7PZTySUOLBuch/vR6U= +github.com/hanwen/go-fuse/v2 v2.11.0 h1:CGVkJh9gRz0pTRMADNcqdFl3ec/5QbE/Vx1Gl7ESozM= +github.com/hanwen/go-fuse/v2 v2.11.0/go.mod h1:aU7NkGYZUmuJrZapoI3mEcNve7PZTySUOLBuch/vR6U= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= diff --git a/vendor/github.com/hanwen/go-fuse/v2/AUTHORS b/vendor/github.com/hanwen/go-fuse/v2/AUTHORS index 09a049f4..f022d8fb 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/AUTHORS +++ b/vendor/github.com/hanwen/go-fuse/v2/AUTHORS @@ -68,6 +68,7 @@ Yufeng Cheng ZheNing Hu Zoey Greer abitduck +chenpu <1102509144@163.com> companycy hotaery <626910647@qq.com> lch diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/api.go b/vendor/github.com/hanwen/go-fuse/v2/fs/api.go index 3cbd5145..1ab40a5f 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/api.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/api.go @@ -636,8 +636,7 @@ type NodeRenamer interface { // FileHandle. Files that have such dynamic content should return the // FOPEN_DIRECT_IO flag from their `Open` method. See directio_test.go // for an example. -type FileHandle interface { -} +type FileHandle any // FilePassthroughFder is a file backed by a physical // file. PassthroughFd should return an open file descriptor (and diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go b/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go index 96782ca9..65f9b136 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go @@ -153,7 +153,7 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten return ops.embed() } -func (b *rawBridge) logf(format string, args ...interface{}) { +func (b *rawBridge) logf(format string, args ...any) { if b.options.Logger != nil { b.options.Logger.Printf(format, args...) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/bridge_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fs/bridge_linux.go index ba07cb41..17ddbcad 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/bridge_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/bridge_linux.go @@ -42,13 +42,13 @@ func (b *rawBridge) Statx(cancel <-chan struct{}, in *fuse.StatxIn, out *fuse.St errno := syscall.ENOSYS if sx, ok := n.ops.(NodeStatxer); ok { errno = sx.Statx(ctx, fh, in.SxFlags, in.SxMask, out) - } else if fsx, ok := n.ops.(FileStatxer); ok { + } else if fsx, ok := fh.(FileStatxer); ok { errno = fsx.Statx(ctx, in.SxFlags, in.SxMask, out) } if errno == 0 { if out.Ino != 0 && n.stableAttr.Ino > 1 && out.Ino != n.stableAttr.Ino { - b.logf("warning: rawBridge.getattr: overriding ino %d with %d", out.Ino, n.stableAttr.Ino) + b.logf("warning: rawBridge.Statx: overriding ino %d with %d", out.Ino, n.stableAttr.Ino) } out.Ino = n.stableAttr.Ino out.Mode = (out.Statx.Mode & 07777) | uint16(n.stableAttr.Mode) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go b/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go index 17a9915a..75eecd49 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go @@ -209,9 +209,13 @@ func (ds *loopbackDirStream) Ioctl(ctx context.Context, cmd uint32, arg uint64, argWord := uintptr(arg) ioc := ioctl.Command(cmd) - if ioc.Read() { - argWord = uintptr(unsafe.Pointer(&input[0])) + if ioc.Read() && ioc.Write() { + // The kernel updates the buffer in place. + copy(output, input) + argWord = uintptr(unsafe.Pointer(&output[0])) } else if ioc.Write() { + argWord = uintptr(unsafe.Pointer(&input[0])) + } else if ioc.Read() { argWord = uintptr(unsafe.Pointer(&output[0])) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/files.go b/vendor/github.com/hanwen/go-fuse/v2/fs/files.go index 7a440f82..58b52dd4 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/files.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/files.go @@ -6,7 +6,8 @@ package fs import ( "context" - "sync" + "errors" + "os" "syscall" "unsafe" @@ -16,12 +17,24 @@ import ( "golang.org/x/sys/unix" ) -// NewLoopbackFile creates a FileHandle out of a file descriptor. All -// operations are implemented. When using the Fd from a *os.File, call -// syscall.Dup() on the fd, to avoid os.File's finalizer from closing -// the file descriptor. +// NewLoopbackFile creates a FileHandle out of a file descriptor. +// +// This function is hard to use correctly. Most callers should use +// NewLoopbackFileFromOS instead. +// +// All operations are implemented. NewLoopbackFile takes ownership of the +// file descriptor: it is closed on Release, or when the FileHandle is +// garbage collected. func NewLoopbackFile(fd int) FileHandle { - return &LoopbackFile{fd: fd} + // Wart: this should return *LoopbackFile instead. + return NewLoopbackFileFromOS(os.NewFile(uintptr(fd), "")) +} + +// NewLoopbackFileFromOS creates a FileHandle out of a *os.File. It +// takes ownership of the file: it is closed on Release, and callers +// should not use it afterwards. +func NewLoopbackFileFromOS(f *os.File) *LoopbackFile { + return &LoopbackFile{f: f} } // LoopbackFile is a FileHandle that implements all the FileXxxx @@ -30,8 +43,7 @@ func NewLoopbackFile(fd int) FileHandle { // NewLoopbackFile(). This type is public so it can be used as a basis // for other FileHandle implementations. type LoopbackFile struct { - mu sync.Mutex - fd int + f *os.File } var _ = (FileHandle)((*LoopbackFile)(nil)) @@ -50,59 +62,73 @@ var _ = (FileAllocater)((*LoopbackFile)(nil)) var _ = (FilePassthroughFder)((*LoopbackFile)(nil)) var _ = (FileIoctler)((*LoopbackFile)(nil)) +func (f *LoopbackFile) withFd(fn func(fd int) syscall.Errno) syscall.Errno { + sc, err := f.f.SyscallConn() + if err != nil { + return syscall.EBADF + } + errno := syscall.EBADF + sc.Control(func(fd uintptr) { + errno = fn(int(fd)) + }) + return errno +} + func (f *LoopbackFile) PassthroughFd() (int, bool) { - // This Fd is not accessed concurrently, but lock anyway for uniformity. - f.mu.Lock() - defer f.mu.Unlock() - return f.fd, true + // The fd outlives the Control call below. It must stay alive + // until the the fd is reported to the kernel. (In the normal + // case, it survives until Release) + var fd int + errno := f.withFd(func(d int) syscall.Errno { + fd = d + return OK + }) + return fd, errno == OK } func (f *LoopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) { - f.mu.Lock() - defer f.mu.Unlock() - r := fuse.ReadResultFd(uintptr(f.fd), off, len(buf)) - return r, OK + errno = f.withFd(func(fd int) syscall.Errno { + res = fuse.ReadResultFd(uintptr(fd), off, len(buf)) + return OK + }) + return res, errno } func (f *LoopbackFile) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) { - f.mu.Lock() - defer f.mu.Unlock() - n, err := syscall.Pwrite(f.fd, data, off) - return uint32(n), ToErrno(err) + var n int + errno := f.withFd(func(fd int) syscall.Errno { + var err error + n, err = syscall.Pwrite(fd, data, off) + return ToErrno(err) + }) + return uint32(n), errno } func (f *LoopbackFile) Release(ctx context.Context) syscall.Errno { - f.mu.Lock() - defer f.mu.Unlock() - if f.fd != -1 { - err := syscall.Close(f.fd) - f.fd = -1 - return ToErrno(err) + err := f.f.Close() + if errors.Is(err, os.ErrClosed) { + return syscall.EBADF } - return syscall.EBADF + return ToErrno(err) } func (f *LoopbackFile) Flush(ctx context.Context) syscall.Errno { - f.mu.Lock() - defer f.mu.Unlock() - // Since Flush() may be called for each dup'd fd, we don't - // want to really close the file, we just want to flush. This - // is achieved by closing a dup'd fd. - newFd, err := syscall.Dup(f.fd) - - if err != nil { - return ToErrno(err) - } - err = syscall.Close(newFd) - return ToErrno(err) + return f.withFd(func(fd int) syscall.Errno { + // Since Flush() may be called for each dup'd fd, we don't + // want to really close the file, we just want to flush. This + // is achieved by closing a dup'd fd. + newFd, err := syscall.Dup(fd) + if err != nil { + return ToErrno(err) + } + return ToErrno(syscall.Close(newFd)) + }) } func (f *LoopbackFile) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) { - f.mu.Lock() - defer f.mu.Unlock() - r := ToErrno(syscall.Fsync(f.fd)) - - return r + return f.withFd(func(fd int) syscall.Errno { + return ToErrno(syscall.Fsync(fd)) + }) } const ( @@ -112,13 +138,13 @@ const ( ) func (f *LoopbackFile) Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (errno syscall.Errno) { - f.mu.Lock() - defer f.mu.Unlock() - flk := syscall.Flock_t{} - lk.ToFlockT(&flk) - errno = ToErrno(syscall.FcntlFlock(uintptr(f.fd), _OFD_GETLK, &flk)) - out.FromFlockT(&flk) - return + return f.withFd(func(fd int) syscall.Errno { + flk := syscall.Flock_t{} + lk.ToFlockT(&flk) + errno := ToErrno(syscall.FcntlFlock(uintptr(fd), _OFD_GETLK, &flk)) + out.FromFlockT(&flk) + return errno + }) } func (f *LoopbackFile) Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) { @@ -130,35 +156,35 @@ func (f *LoopbackFile) Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLo } func (f *LoopbackFile) setLock(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, blocking bool) (errno syscall.Errno) { - f.mu.Lock() - defer f.mu.Unlock() - if (flags & fuse.FUSE_LK_FLOCK) != 0 { - var op int - switch lk.Typ { - case syscall.F_RDLCK: - op = syscall.LOCK_SH - case syscall.F_WRLCK: - op = syscall.LOCK_EX - case syscall.F_UNLCK: - op = syscall.LOCK_UN - default: - return syscall.EINVAL - } - if !blocking { - op |= syscall.LOCK_NB - } - return ToErrno(syscall.Flock(f.fd, op)) - } else { - flk := syscall.Flock_t{} - lk.ToFlockT(&flk) - var op int - if blocking { - op = _OFD_SETLKW + return f.withFd(func(fd int) syscall.Errno { + if (flags & fuse.FUSE_LK_FLOCK) != 0 { + var op int + switch lk.Typ { + case syscall.F_RDLCK: + op = syscall.LOCK_SH + case syscall.F_WRLCK: + op = syscall.LOCK_EX + case syscall.F_UNLCK: + op = syscall.LOCK_UN + default: + return syscall.EINVAL + } + if !blocking { + op |= syscall.LOCK_NB + } + return ToErrno(syscall.Flock(fd, op)) } else { - op = _OFD_SETLK + flk := syscall.Flock_t{} + lk.ToFlockT(&flk) + var op int + if blocking { + op = _OFD_SETLKW + } else { + op = _OFD_SETLK + } + return ToErrno(syscall.FcntlFlock(uintptr(fd), op, &flk)) } - return ToErrno(syscall.FcntlFlock(uintptr(f.fd), op, &flk)) - } + }) } func (f *LoopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { @@ -170,19 +196,21 @@ func (f *LoopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fus } func (f *LoopbackFile) fchmod(mode uint32) syscall.Errno { - f.mu.Lock() - defer f.mu.Unlock() - return ToErrno(syscall.Fchmod(f.fd, mode)) + return f.withFd(func(fd int) syscall.Errno { + return ToErrno(syscall.Fchmod(fd, mode)) + }) } func (f *LoopbackFile) fchown(uid, gid int) syscall.Errno { - f.mu.Lock() - defer f.mu.Unlock() - return ToErrno(syscall.Fchown(f.fd, uid, gid)) + return f.withFd(func(fd int) syscall.Errno { + return ToErrno(syscall.Fchown(fd, uid, gid)) + }) } func (f *LoopbackFile) ftruncate(sz uint64) syscall.Errno { - return ToErrno(syscall.Ftruncate(f.fd, int64(sz))) + return f.withFd(func(fd int) syscall.Errno { + return ToErrno(syscall.Ftruncate(fd, int64(sz))) + }) } func (f *LoopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.Errno { @@ -210,6 +238,14 @@ func (f *LoopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall. } } + // Truncate before setting times, so an explicit mtime is not + // clobbered by the truncate. + if sz, ok := in.GetSize(); ok { + if errno := f.ftruncate(sz); errno != 0 { + return errno + } + } + mtime, mok := in.GetMTime() atime, aok := in.GetATime() @@ -227,57 +263,55 @@ func (f *LoopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall. return errno } } - - if sz, ok := in.GetSize(); ok { - if errno := f.ftruncate(sz); errno != 0 { - return errno - } - } return OK } func (f *LoopbackFile) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno { - f.mu.Lock() - defer f.mu.Unlock() - st := syscall.Stat_t{} - err := syscall.Fstat(f.fd, &st) - if err != nil { - return ToErrno(err) - } - a.FromStat(&st) + return f.withFd(func(fd int) syscall.Errno { + st := syscall.Stat_t{} + err := syscall.Fstat(fd, &st) + if err != nil { + return ToErrno(err) + } + a.FromStat(&st) - return OK + return OK + }) } func (f *LoopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) { - f.mu.Lock() - defer f.mu.Unlock() - n, err := unix.Seek(f.fd, int64(off), int(whence)) - return uint64(n), ToErrno(err) + var n int64 + errno := f.withFd(func(fd int) syscall.Errno { + var err error + n, err = unix.Seek(fd, int64(off), int(whence)) + return ToErrno(err) + }) + return uint64(n), errno } func (f *LoopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno { - f.mu.Lock() - defer f.mu.Unlock() - err := fallocate.Fallocate(f.fd, mode, int64(off), int64(sz)) - if err != nil { - return ToErrno(err) - } - return OK + return f.withFd(func(fd int) syscall.Errno { + return ToErrno(fallocate.Fallocate(fd, mode, int64(off), int64(sz))) + }) } func (f *LoopbackFile) Ioctl(ctx context.Context, cmd uint32, arg uint64, input []byte, output []byte) (result int32, errno syscall.Errno) { - f.mu.Lock() - defer f.mu.Unlock() - - argWord := uintptr(arg) - ioc := ioctl.Command(cmd) - if ioc.Read() { - argWord = uintptr(unsafe.Pointer(&input[0])) - } else if ioc.Write() { - argWord = uintptr(unsafe.Pointer(&output[0])) - } + errno = f.withFd(func(fd int) syscall.Errno { + argWord := uintptr(arg) + ioc := ioctl.Command(cmd) + if ioc.Read() && ioc.Write() { + // The kernel updates the buffer in place. + copy(output, input) + argWord = uintptr(unsafe.Pointer(&output[0])) + } else if ioc.Write() { + argWord = uintptr(unsafe.Pointer(&input[0])) + } else if ioc.Read() { + argWord = uintptr(unsafe.Pointer(&output[0])) + } - res, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f.fd), uintptr(cmd), argWord) - return int32(res), errno + res, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(cmd), argWord) + result = int32(res) + return errno + }) + return result, errno } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go index 115d863f..c2b18987 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go @@ -27,6 +27,7 @@ func (f *LoopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { } } tv := utimens.Fill(a, m, &attr.Attr) - err := syscall.Futimes(int(f.fd), tv) - return ToErrno(err) + return f.withFd(func(fd int) syscall.Errno { + return ToErrno(syscall.Futimes(fd, tv)) + }) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go index 4da9fb4f..3318153f 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go @@ -33,14 +33,14 @@ func setStatxBlocks(out *fuse.Statx) { } func (f *LoopbackFile) Statx(ctx context.Context, flags uint32, mask uint32, out *fuse.StatxOut) syscall.Errno { - f.mu.Lock() - defer f.mu.Unlock() - st := unix.Statx_t{} - err := unix.Statx(f.fd, "", int(flags), int(mask), &st) - if err != nil { - return ToErrno(err) - } - out.FromStatx(&st) - - return OK + return f.withFd(func(fd int) syscall.Errno { + st := unix.Statx_t{} + err := unix.Statx(fd, "", int(flags), int(mask), &st) + if err != nil { + return ToErrno(err) + } + out.FromStatx(&st) + + return OK + }) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/files_unix.go b/vendor/github.com/hanwen/go-fuse/v2/fs/files_unix.go index bdf00e30..455b329f 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/files_unix.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/files_unix.go @@ -15,8 +15,9 @@ func (f *LoopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { var ts [2]syscall.Timespec ts[0] = fuse.UtimeToTimespec(a) ts[1] = fuse.UtimeToTimespec(m) - err := futimens(int(f.fd), &ts) - return ToErrno(err) + return f.withFd(func(fd int) syscall.Errno { + return ToErrno(futimens(fd, &ts)) + }) } // futimens - futimens(3) calls utimensat(2) with "pathname" set to null and diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go b/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go index 3271415f..57b77887 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go @@ -742,14 +742,15 @@ func (n *Inode) NotifyEntry(name string) syscall.Errno { // in response. If the receiver Inode must be forgotten too it must // be included in the argument separately. func (n *Inode) NotifyPrune(nodes []*Inode) syscall.Errno { - if n.bridge.server == nil { + server, ok := n.bridge.server.(*fuse.Server) + if !ok { return syscall.ENOSYS } ids := make([]uint64, 0, len(nodes)) for _, n := range nodes { ids = append(ids, n.nodeId) } - status := n.bridge.server.(*fuse.Server).PruneNotify(ids) + status := server.PruneNotify(ids) return syscall.Errno(status) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go index ee81320c..18ad4676 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go @@ -165,7 +165,7 @@ func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32 n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { - syscall.Rmdir(p) + syscall.Unlink(p) return nil, ToErrno(err) } @@ -244,20 +244,20 @@ var _ = (NodeCreater)((*LoopbackNode)(nil)) func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) { p := filepath.Join(n.path(), name) flags = flags &^ syscall.O_APPEND - fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode) + f, err := os.OpenFile(p, int(flags)|os.O_CREATE, os.FileMode(mode)) if err != nil { return nil, nil, 0, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} - if err := syscall.Fstat(fd, &st); err != nil { - syscall.Close(fd) + if err := syscall.Fstat(int(f.Fd()), &st); err != nil { + f.Close() return nil, nil, 0, ToErrno(err) } node := n.RootData.newNode(n.EmbeddedInode(), name, &st) ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st)) - lf := NewLoopbackFile(fd) + lf := NewLoopbackFileFromOS(f) out.FromStat(&st) return ch, lf, 0, 0 @@ -320,9 +320,17 @@ func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fu var _ = (NodeLinker)((*LoopbackNode)(nil)) func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { + e2, ok := target.(loopbackNodeEmbedder) + if !ok { + return nil, syscall.EXDEV + } + + if e2.loopbackNode().RootData != n.RootData { + return nil, syscall.EXDEV + } p := filepath.Join(n.path(), name) - err := syscall.Link(filepath.Join(n.RootData.Path, target.EmbeddedInode().Path(nil)), p) + err := syscall.Link(e2.loopbackNode().path(), p) if err != nil { return nil, ToErrno(err) } @@ -418,7 +426,9 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt p := n.path() fsa, ok := f.(FileSetattrer) if ok && fsa != nil { - fsa.Setattr(ctx, in, out) + if errno := fsa.Setattr(ctx, in, out); errno != 0 { + return errno + } } else { if m, ok := in.GetMode(); ok { if err := syscall.Chmod(p, m); err != nil { @@ -442,6 +452,14 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt } } + // Truncate before setting times, so an explicit mtime is + // not clobbered by the truncate. + if sz, ok := in.GetSize(); ok { + if err := syscall.Truncate(p, int64(sz)); err != nil { + return ToErrno(err) + } + } + mtime, mok := in.GetMTime() atime, aok := in.GetATime() @@ -466,12 +484,6 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt return ToErrno(err) } } - - if sz, ok := in.GetSize(); ok { - if err := syscall.Truncate(p, int64(sz)); err != nil { - return ToErrno(err) - } - } } fga, ok := f.(FileGetattrer) @@ -513,7 +525,7 @@ var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil)) func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, - len uint64, flags uint64) (uint32, syscall.Errno) { + len uint64, flags uint64) (count uint32, errno syscall.Errno) { lfIn, ok := fhIn.(*LoopbackFile) if !ok { return 0, unix.ENOTSUP @@ -524,8 +536,13 @@ func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, } signedOffIn := int64(offIn) signedOffOut := int64(offOut) - doCopyFileRange(lfIn.fd, signedOffIn, lfOut.fd, signedOffOut, int(len), int(flags)) - return 0, syscall.ENOSYS + lfIn.withFd(func(fdIn int) syscall.Errno { + return lfOut.withFd(func(fdOut int) syscall.Errno { + count, errno = doCopyFileRange(fdIn, signedOffIn, fdOut, signedOffOut, int(len), int(flags)) + return OK + }) + }) + return count, errno } // NewLoopbackRoot returns a root node for a loopback file system whose diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go index 298a3fb6..d93c79f8 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux // Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/mem.go b/vendor/github.com/hanwen/go-fuse/v2/fs/mem.go index fac8d6af..fa7858ea 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/mem.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/mem.go @@ -32,17 +32,14 @@ var _ = (NodeAllocater)((*MemRegularFile)(nil)) func (f *MemRegularFile) Allocate(ctx context.Context, fh FileHandle, off uint64, size uint64, mode uint32) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() - oldSz := len(f.Data) - if uint64(cap(f.Data)) < off+size { - n := make([]byte, off+size) + if keepSizeMode(mode) { + return 0 + } + if end := off + size; end > uint64(len(f.Data)) { + n := make([]byte, end) copy(n, f.Data) f.Data = n } - if keepSizeMode(mode) { - f.Data = f.Data[:oldSz] - } else if len(f.Data) < int(off+size) { - f.Data = f.Data[:off+size] - } return 0 } @@ -79,7 +76,13 @@ func (f *MemRegularFile) Setattr(ctx context.Context, fh FileHandle, in *fuse.Se f.mu.Lock() defer f.mu.Unlock() if sz, ok := in.GetSize(); ok { - f.Data = f.Data[:sz] + if sz <= uint64(len(f.Data)) { + f.Data = f.Data[:sz] + } else { + n := make([]byte, sz) + copy(n, f.Data) + f.Data = n + } } out.Attr = f.Attr out.Size = uint64(len(f.Data)) @@ -93,10 +96,8 @@ func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() - end := int(off) + len(dest) - if end > len(f.Data) { - end = len(f.Data) - } + off = min(off, int64(len(f.Data))) + end := min(int(off)+len(dest), len(f.Data)) return fuse.ReadResultData(f.Data[off:end]), OK } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go index d1aff586..d8536bc0 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go @@ -167,8 +167,29 @@ type MountOptions struct { // Concurrency for synchronous I/O is not limited. MaxBackground int + // MaxInflightRequestBytes controls the number of bytes used for + // request structs and input buffers checked out by go-fuse. This + // includes buffers used by readers waiting on the kernel and requests + // being processed concurrently. + // + // It also applies to requests that do not expect a reply, such as + // FORGET and BATCH_FORGET. If unset, it defaults to math.MaxInt. If + // set smaller than the bytes needed for a single request, one request + // is still allowed through. + MaxInflightRequestBytes int + + // CongestionThreshold is the in-flight async-request count at which + // the kernel marks the FUSE backing-dev as congested, throttling new + // submissions. It corresponds to + // /sys/fs/fuse/connections//congestion_threshold. + // + // If 0, go-fuse falls back to the kernel-FUSE convention of + // 3/4 * MaxBackground. The value is silently clamped by the kernel + // to MaxBackground if it is set higher. + CongestionThreshold int + // MaxWrite is the max size for read and write requests. If 0, use - // go-fuse default (currently 64 kiB). + // go-fuse default (currently 128 kiB). // This number is internally capped at MAX_KERNEL_WRITE (higher values don't make // sense). // @@ -228,8 +249,9 @@ type MountOptions struct { // locking wrapper. SingleThreaded bool - // DisableXAttrs, if set, returns ENOSYS for Getxattr calls, so the kernel - // does not issue any Xattr operations at all. + // DisableXAttrs, if set, returns ENOSYS for Getxattr, Setxattr and + // Removexattr calls, so the kernel does not issue any Xattr operations + // at all. DisableXAttrs bool // Debug, if set, enables verbose debugging information. @@ -335,11 +357,16 @@ type MountOptions struct { // DisableSplice, if set, disables splicing from files to the FUSE device. DisableSplice bool + // PanicHandler is called if an FS routine panics. The handler + // should return a nonzero status. If not set, the default is + // to print a stack trace and return EIO. + PanicHandler func(any) Status + // MaxStackDepth is the maximum stacking depth for passthrough files. // If unset, the default is 1. MaxStackDepth int - // RawFileSystem, if set, enables an ID-mapped mount if the Kernel supports + // IDMappedMount, if set, enables an ID-mapped mount if the Kernel supports // it. // // An ID-mapped mount allows the device to be mounted on the system with the diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go index 6da37ffb..f3350d97 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go @@ -41,7 +41,7 @@ func (p *bufferPool) getPool(pageCount int, delta int) *sync.Pool { } if p.buffersBySize[pageCount] == nil { p.buffersBySize[pageCount] = &sync.Pool{ - New: func() interface{} { return make([]byte, pageSize*pageCount) }, + New: func() any { return make([]byte, pageSize*pageCount) }, } } p.countersBySize[pageCount] += delta @@ -51,10 +51,7 @@ func (p *bufferPool) getPool(pageCount int, delta int) *sync.Pool { // AllocBuffer creates a buffer of at least the given size. After use, // it should be deallocated with FreeBuffer(). func (p *bufferPool) AllocBuffer(size uint32) []byte { - sz := int(size) - if sz < pageSize { - sz = pageSize - } + sz := max(int(size), pageSize) if sz%pageSize != 0 { sz += pageSize diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go index 97c35052..06d04023 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go @@ -40,5 +40,5 @@ const ( // being executed. FMODE_EXEC = 0x20 - logicalBlockSize = 512 + logicalBlockSize = 4096 ) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go index 557044f6..f8e6edf4 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go @@ -51,7 +51,7 @@ func NewContext(ctx context.Context, caller *Caller) context.Context { return context.WithValue(ctx, callerKey, caller) } -func (c *Context) Value(key interface{}) interface{} { +func (c *Context) Value(key any) any { if key == callerKey { return &c.Caller } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go index b7d6f9f8..8f87f68a 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go @@ -119,9 +119,9 @@ func (l *DirEntryList) addDirEntry(e *DirEntry, prefix int) bool { copy(l.buf[oldLen:], e.Name) oldLen += len(e.Name) - if padding > 0 { - l.buf[oldLen] = 0 - } + // The buffer is recycled, so the padding may hold data from + // a previous request. + clear(l.buf[oldLen : oldLen+padding]) l.Offset = dirent.Off return true } @@ -177,6 +177,9 @@ func modeToType(mode uint32) uint32 { // be needed when a directory changes while READDIRPLUS is running. // Only the file type bits of mode are considered, the rest is masked out. func (l *DirEntryList) FixMode(mode uint32) { + if l.lastDirent == nil { + return + } l.lastDirent.Typ = modeToType(mode) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd.go new file mode 100644 index 00000000..a6957379 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd.go @@ -0,0 +1,241 @@ +// Copyright 2026 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "log" + "os" + "sync" + "syscall" + "unsafe" +) + +// fuseFD owns the per-FUSE-fd state: the fd itself, the read-side +// bookkeeping (reqReaders, inflightRequestBytes), and the WaitGroup +// tracking reader goroutines on this fd. The pools are shared with the +// owning Server via pointers, and a back pointer to the Server gives the +// per-fd methods access to shared configuration and callbacks. +// +// Today a Server has a single fuseFD; this struct exists so that adding +// support for FUSE_DEV_IOC_CLONE'd fds is a matter of widening the field +// to a slice. +type fuseFD struct { + server *Server + + // I/O with kernel and daemon. file owns the FUSE fd; conn is its + // RawConn. Running syscalls through conn holds a reference on the + // fd, so concurrent writers and close() are safe against the fd + // number being reused without any explicit locking. + file *os.File + conn syscall.RawConn + + reqMu sync.Mutex + reqReaders int + inflightRequestBytes int + + // loops tracks reader goroutines servicing fd. + loops sync.WaitGroup + + // Shared pools owned by Server. + reqPool *sync.Pool + readPool *sync.Pool + buffers *bufferPool + + // Accounting constants, set once at server construction. + reqAllocBytes int + readBufBytes int +} + +// newFuseFD returns a fuseFD bound to ms and ready to read from fd. +// Ownership of fd passes to the returned fuseFD's *os.File. All shared +// state (pools, buffer pool, accounting constants) is wired up here. +func (ms *Server) newFuseFD(fd int) (*fuseFD, error) { + file := os.NewFile(uintptr(fd), "/dev/fuse") + conn, err := file.SyscallConn() + if err != nil { + file.Close() + return nil, err + } + _, readBufBytes, reqAllocBytes := requestAccountingSizes(ms.opts.MaxWrite) + return &fuseFD{ + server: ms, + file: file, + conn: conn, + reqPool: &ms.reqPool, + readPool: &ms.readPool, + buffers: &ms.buffers, + reqAllocBytes: reqAllocBytes, + readBufBytes: readBufBytes, + }, nil +} + +// withFD runs f with the underlying FUSE file descriptor, holding a +// reference on it for the duration so the fd cannot be closed (and its +// number reused) while f runs. This is what lets concurrent writers and +// close() run without a serializing mutex. The error is non-nil only +// when the fd is already closed, in which case f is not called. +func (r *fuseFD) withFD(f func(fd int)) error { + return r.conn.Control(func(fd uintptr) { + f(int(fd)) + }) +} + +// writevFD writes iov to the FUSE fd, holding a reference against +// concurrent close. The error is the underlying syscall error, or a +// non-syscall error if the fd is already closed. +func (r *fuseFD) writevFD(iov [][]byte) (int, error) { + var n int + var err error + if cerr := r.withFD(func(fd int) { + n, err = writev(fd, iov) + }); cerr != nil { + return 0, cerr + } + return n, err +} + +// readRequest reads one request from the kernel. Returns nil, OK if +// there are too many concurrent readers or insufficient request-bytes +// budget. +func (r *fuseFD) readRequest() (req *requestAlloc, code Status) { + ms := r.server + r.reqMu.Lock() + if r.reqReaders > ms.maxReaders || !r.reserveRequestBytes() { + r.reqMu.Unlock() + return nil, OK + } + r.reqReaders++ + r.reqMu.Unlock() + + req = r.reqPool.Get().(*requestAlloc) + dest := r.readPool.Get().([]byte) + + var n int + err := handleEINTR(func() error { + var err error + if cerr := r.withFD(func(fd int) { + n, err = syscall.Read(fd, dest) + }); cerr != nil { + return cerr + } + return err + }) + if err != nil { + r.reqMu.Lock() + r.putReadBuf(dest) + r.putReq(req) + r.reqReaders-- + r.reqMu.Unlock() + return nil, ToStatus(err) + } + + r.reqMu.Lock() + defer r.reqMu.Unlock() + gobbled := req.setInput(dest[:n]) + if len(req.inputBuf) < int(unsafe.Sizeof(InHeader{})) { + log.Printf("Short read for input header: %v", req.inputBuf) + r.putReadBuf(dest) + r.putReq(req) + r.reqReaders-- + return nil, EINVAL + } + opCode := ((*InHeader)(unsafe.Pointer(&req.inputBuf[0]))).Opcode + /* These messages don't expect reply, so they cost nothing for + the kernel to send. Make sure we're not overwhelmed by not + spawning a new reader. + */ + needsBackPressure := (opCode == _OP_FORGET || opCode == _OP_BATCH_FORGET) + + if !gobbled { + r.putReadBuf(dest) + } + r.reqReaders-- + if !ms.singleReader && r.reqReaders <= 0 && !needsBackPressure { + r.loops.Add(1) + go ms.loop() + } + + return req, OK +} + +// returnRequest returns a request to the pool of unused requests. +func (r *fuseFD) returnRequest(req *requestAlloc) { + if req.bufferPoolOutputBuf != nil { + r.buffers.FreeBuffer(req.bufferPoolOutputBuf) + req.bufferPoolOutputBuf = nil + } + if req.interrupted { + req.interrupted = false + req.cancel = make(chan struct{}, 0) + } + req.clear() + + r.reqMu.Lock() + if p := req.bufferPoolInputBuf; p != nil { + req.bufferPoolInputBuf = nil + r.putReadBuf(p) + } + r.putReq(req) + r.reqMu.Unlock() +} + +func (r *fuseFD) reserveRequestBytes() bool { + if !r.canReserveRequestBytes() { + return false + } + r.inflightRequestBytes += r.requestBytes() + return true +} + +func (r *fuseFD) canReserveRequestBytes() bool { + return r.inflightRequestBytes == 0 || + r.requestBytes() <= r.server.opts.MaxInflightRequestBytes-r.inflightRequestBytes +} + +// canAcceptAnother wraps canReserveRequestBytes with reqMu, for callers +// that don't already hold the lock. +func (r *fuseFD) canAcceptAnother() bool { + r.reqMu.Lock() + defer r.reqMu.Unlock() + return r.canReserveRequestBytes() +} + +func (r *fuseFD) requestBytes() int { + return r.reqAllocBytes + r.readBufBytes +} + +func (r *fuseFD) putReadBuf(buf []byte) { + r.readPool.Put(buf) + r.inflightRequestBytes -= r.readBufBytes +} + +func (r *fuseFD) putReq(req *requestAlloc) { + r.reqPool.Put(req) + r.inflightRequestBytes -= r.reqAllocBytes +} + +// close closes the underlying FUSE fd. The *os.File waits for in-flight +// RawConn operations (reads, writes, ioctls) before releasing the fd. +func (r *fuseFD) close() error { + return r.file.Close() +} + +func (r *fuseFD) writev(iov [][]byte) (int, syscall.Errno) { + n, err := r.writevFD(iov) + if err == nil { + return n, 0 + } + errno, ok := err.(syscall.Errno) + if !ok { + // The fd is closed; report it as such. + return n, syscall.EBADF + } + if errno == syscall.EINVAL { + // Detail: the kernel returns EINVAL for unsupported + // notify methods. + errno = syscall.ENOSYS + } + return n, errno +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/server_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd_linux.go similarity index 65% rename from vendor/github.com/hanwen/go-fuse/v2/fuse/server_linux.go rename to vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd_linux.go index aab2a414..c7037e9a 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/server_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd_linux.go @@ -6,23 +6,23 @@ package fuse const useSingleReader = false -func (ms *Server) write(req *request) Status { +func (r *fuseFD) write(req *request) Status { if req.outPayloadSize() == 0 { err := handleEINTR(func() error { - _, err := writev(ms.mountFd, [][]byte{req.outHeaderBuf, req.outDataBuf}) + _, err := r.writevFD([][]byte{req.outHeaderBuf, req.outDataBuf}) return err }) return ToStatus(err) } if req.readResult != nil { defer req.readResult.Done() - if ms.canSplice { - err := ms.trySplice(req, req.readResult) + if r.server.canSplice { + err := r.trySplice(req, req.readResult) if err == nil { return OK } if err != errRecoverSplice { - ms.opts.Logger.Println("trySplice:", err) + r.server.opts.Logger.Println("trySplice:", err) } } @@ -30,6 +30,6 @@ func (ms *Server) write(req *request) Status { req.serializeHeader(len(req.outPayload)) } - _, err := writev(ms.mountFd, [][]byte{req.outHeaderBuf, req.outDataBuf, req.outPayload}) + _, err := r.writevFD([][]byte{req.outHeaderBuf, req.outDataBuf, req.outPayload}) return ToStatus(err) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/server_unix.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd_other.go similarity index 70% rename from vendor/github.com/hanwen/go-fuse/v2/fuse/server_unix.go rename to vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd_other.go index c72246ba..ca7fe961 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/server_unix.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/fusefd_other.go @@ -7,10 +7,10 @@ package fuse // do not error-out, meaning that unmount will hang. const useSingleReader = true -func (ms *Server) write(req *request) Status { +func (r *fuseFD) write(req *request) Status { if req.outPayloadSize() == 0 { err := handleEINTR(func() error { - _, err := writev(int(ms.mountFd), [][]byte{req.outHeaderBuf, req.outDataBuf}) + _, err := r.writevFD([][]byte{req.outHeaderBuf, req.outDataBuf}) return err }) return ToStatus(err) @@ -19,13 +19,12 @@ func (ms *Server) write(req *request) Status { if req.readResult != nil { req.outPayload, req.status = req.readResult.Bytes(req.outPayload) req.serializeHeader(len(req.outPayload)) - req.readResult.Done() - req.readResult = nil } - _, err := writev(int(ms.mountFd), [][]byte{req.outHeaderBuf, req.outDataBuf, req.outPayload}) + _, err := r.writevFD([][]byte{req.outHeaderBuf, req.outDataBuf, req.outPayload}) if req.readResult != nil { req.readResult.Done() + req.readResult = nil } return ToStatus(err) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go index 8edc37a9..9c4bcea1 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go @@ -7,6 +7,7 @@ package fuse import ( + "errors" "fmt" "log" "os" @@ -37,9 +38,11 @@ func (code Status) Ok() bool { // ToStatus extracts an errno number from Go error objects. If it // fails, it logs an error and returns ENOSYS. func ToStatus(err error) Status { - switch err { - case nil: + if err == nil { return OK + } + + switch err { case os.ErrPermission: return EPERM case os.ErrExist: @@ -50,18 +53,12 @@ func ToStatus(err error) Status { return EINVAL } - switch t := err.(type) { - case syscall.Errno: - return Status(t) - case *os.SyscallError: - return Status(t.Err.(syscall.Errno)) - case *os.PathError: - return ToStatus(t.Err) - case *os.LinkError: - return ToStatus(t.Err) + var errno syscall.Errno + if errors.As(err, &errno) { + return Status(errno) } log.Println("can't convert error type:", err) - return ENOSYS + return ENOTSUP } func CurrentOwner() *Owner { diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go index 830f138e..7fddae27 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go @@ -29,11 +29,6 @@ func unixgramSocketpair() (l, r *os.File, err error) { // Create a FUSE FS on the specified mount point without using // fusermount. func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { - fd, err = syscall.Open("/dev/fuse", os.O_RDWR, 0) // use syscall.Open since we want an int fd - if err != nil { - return - } - // managed to open dev/fuse, attempt to mount source := opts.FsName if source == "" { @@ -51,6 +46,11 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd return } + fd, err = syscall.Open("/dev/fuse", os.O_RDWR, 0) // use syscall.Open since we want an int fd + if err != nil { + return + } + // some values we need to pass to mount - we do as fusermount does. // override possible since opts.Options comes after. // diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go index 98bf8a9d..efbe61ec 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go @@ -87,7 +87,17 @@ const ( //////////////////////////////////////////////////////////////// func doInit(server *protocolServer, req *request) { - input := (*InitIn)(req.inData()) + var input *InitIn + if len(req.inputBuf) < int(unsafe.Sizeof(InitIn{})) { + // Kernels predating protocol 7.36 send a 16-byte INIT + // payload; zero-extend it so the full struct can be + // read safely. + var extended InitIn + copy(unsafe.Slice((*byte)(unsafe.Pointer(&extended)), unsafe.Sizeof(extended)), req.inputBuf) + input = &extended + } else { + input = (*InitIn)(req.inData()) + } if input.Major != _FUSE_KERNEL_VERSION { log.Printf("Major versions does not match. Given %d, want %d\n", input.Major, _FUSE_KERNEL_VERSION) req.status = EIO @@ -106,13 +116,13 @@ func doInit(server *protocolServer, req *request) { server.opts.ExtraCapabilities) if server.opts.EnableLocks { - kernelFlags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS + kernelFlags |= input.Flags64() & (CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS) } if server.opts.EnableSymlinkCaching { - kernelFlags |= CAP_CACHE_SYMLINKS + kernelFlags |= input.Flags64() & CAP_CACHE_SYMLINKS } if server.opts.EnableAcl { - kernelFlags |= CAP_POSIX_ACL + kernelFlags |= input.Flags64() & CAP_POSIX_ACL } if server.opts.ExplicitDataCacheControl { @@ -129,13 +139,18 @@ func doInit(server *protocolServer, req *request) { // 128kiB. maxPages := (server.opts.MaxWrite-1)/syscall.Getpagesize() + 1 // Round up + congestionThreshold := server.opts.CongestionThreshold + if congestionThreshold <= 0 { + congestionThreshold = server.opts.MaxBackground * 3 / 4 + } + out := (*InitOut)(req.outData()) *out = InitOut{ Major: _FUSE_KERNEL_VERSION, Minor: _OUR_MINOR_VERSION, MaxReadAhead: input.MaxReadAhead, MaxWrite: uint32(server.opts.MaxWrite), - CongestionThreshold: uint16(server.opts.MaxBackground * 3 / 4), + CongestionThreshold: uint16(congestionThreshold), MaxBackground: uint16(server.opts.MaxBackground), MaxPages: uint16(maxPages), MaxStackDepth: uint32(server.opts.MaxStackDepth), @@ -208,7 +223,7 @@ func doNotifyReply(server *protocolServer, req *request) { delete(server.retrieveTab, reply.Unique) server.retrieveMu.Unlock() - badf := func(format string, argv ...interface{}) { + badf := func(format string, argv ...any) { server.opts.Logger.Printf("notify reply: "+format, argv...) } @@ -387,6 +402,10 @@ func doFsyncDir(server *protocolServer, req *request) { } func doSetXAttr(server *protocolServer, req *request) { + if server.opts.DisableXAttrs { + req.status = ENOSYS + return + } i := bytes.IndexByte(req.inPayload, 0) if i < 0 { req.status = EINVAL @@ -396,6 +415,10 @@ func doSetXAttr(server *protocolServer, req *request) { } func doRemoveXAttr(server *protocolServer, req *request) { + if server.opts.DisableXAttrs { + req.status = ENOSYS + return + } req.status = server.fileSystem.RemoveXAttr(req.cancel, req.inHeader(), req.filename()) } @@ -445,6 +468,10 @@ func doIoctl(server *protocolServer, req *request) { req.outPayload) } +func doPoll(server *protocolServer, req *request) { + req.status = ENOSYS +} + func doDestroy(server *protocolServer, req *request) { req.status = OK } @@ -486,7 +513,7 @@ func doInterrupt(server *protocolServer, req *request) { //////////////////////////////////////////////////////////////// type operationFunc func(*protocolServer, *request) -type castPointerFunc func(unsafe.Pointer) interface{} +type castPointerFunc func(unsafe.Pointer) any type operationHandler struct { OpCode int @@ -495,8 +522,8 @@ type operationHandler struct { InputSize uintptr OutputSize uintptr - InType interface{} - OutType interface{} + InType any + OutType any FileNames int FileNameOut bool SuppressReply bool @@ -507,7 +534,7 @@ var operationHandlers []*operationHandler func operationName(op uint32) string { h := getHandler(op) if h == nil { - return "unknown" + return fmt.Sprintf("opcode %d", op) } return h.Name } @@ -519,7 +546,7 @@ func getHandler(o uint32) *operationHandler { return operationHandlers[o] } -// maximum size of all input headers +// Maximum size of all input headers. var maxInputSize uintptr func init() { @@ -635,6 +662,7 @@ func init() { _OP_RENAME: doRename, _OP_STATFS: doStatFs, _OP_IOCTL: doIoctl, + _OP_POLL: doPoll, _OP_DESTROY: doDestroy, _OP_NOTIFY_REPLY: doNotifyReply, _OP_FALLOCATE: doFallocate, @@ -648,7 +676,7 @@ func init() { } // Outputs. - for op, f := range map[uint32]interface{}{ + for op, f := range map[uint32]any{ _OP_BMAP: _BmapOut{}, _OP_COPY_FILE_RANGE: WriteOut{}, _OP_CREATE: CreateOut{}, @@ -683,7 +711,7 @@ func init() { } // Inputs. - for op, f := range map[uint32]interface{}{ + for op, f := range map[uint32]any{ _OP_ACCESS: AccessIn{}, _OP_BATCH_FORGET: _BatchForgetIn{}, _OP_BMAP: _BmapIn{}, @@ -724,11 +752,7 @@ func init() { _OP_COPY_FILE_RANGE_64: CopyFileRangeIn{}, } { operationHandlers[op].InType = f - sz := typSize(f) - operationHandlers[op].InputSize = sz - if maxInputSize < sz { - maxInputSize = sz - } + operationHandlers[op].InputSize = typSize(f) } // File name args. @@ -759,5 +783,8 @@ func checkFixedBufferSize() { if h.OutputSize > unsafe.Sizeof(r.outDataInline) { log.Panicf("request output buffer too small: code %v, sz %d %v", code, h.OutputSize, h) } + if maxInputSize < h.InputSize { + maxInputSize = h.InputSize + } } } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode_darwin.go new file mode 100644 index 00000000..1036a7cb --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode_darwin.go @@ -0,0 +1,26 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import "unsafe" + +const _OP_MONITOR = uint32(60) + +func doMonitor(_ *protocolServer, req *request) { + // macFUSE sends watcher-monitor notifications for files and directories. + // They are advisory and do not require a reply. + req.suppressReply = true +} + +func init() { + operationHandlers[_OP_MONITOR] = &operationHandler{ + Name: "MONITOR", + Func: doMonitor, + InputSize: unsafe.Sizeof(MonitorIn{}), + InType: MonitorIn{}, + } + + checkFixedBufferSize() +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/passthrough_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/passthrough_linux.go index df1e3cc8..5efa5d03 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/passthrough_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/passthrough_linux.go @@ -14,6 +14,34 @@ const ( _DEV_IOC_BACKING_CLOSE = 0x4004e502 ) +func (r *fuseFD) registerBackingFd(m *BackingMap) (int32, syscall.Errno) { + var id uintptr + var errno syscall.Errno + if cerr := r.withFD(func(fd int) { + id, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(_DEV_IOC_BACKING_OPEN), uintptr(unsafe.Pointer(m))) + }); cerr != nil { + errno = syscall.EBADF + } + if r.server.opts.Debug { + r.server.opts.Logger.Printf("ioctl: BACKING_OPEN %v: id %d (%v)", m.string(), id, errno) + } + return int32(id), errno +} + +func (r *fuseFD) unregisterBackingFd(id int32) syscall.Errno { + var errno syscall.Errno + if cerr := r.withFD(func(fd int) { + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(_DEV_IOC_BACKING_CLOSE), uintptr(unsafe.Pointer(&id))) + }); cerr != nil { + errno = syscall.EBADF + } + + if r.server.opts.Debug { + r.server.opts.Logger.Printf("ioctl: BACKING_CLOSE id %d: %v", id, errno) + } + return errno +} + // RegisterBackingFd registers the given file descriptor in the // kernel, so the kernel can bypass FUSE and access the backing file // directly for read and write calls. On success a backing ID is @@ -23,24 +51,11 @@ const ( // Open/Create calls should coordinate to return a consistent backing // ID. func (ms *Server) RegisterBackingFd(m *BackingMap) (int32, syscall.Errno) { - ms.writeMu.Lock() - id, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ms.mountFd), uintptr(_DEV_IOC_BACKING_OPEN), uintptr(unsafe.Pointer(m))) - ms.writeMu.Unlock() - if ms.opts.Debug { - ms.opts.Logger.Printf("ioctl: BACKING_OPEN %v: id %d (%v)", m.string(), id, errno) - } - return int32(id), errno + return ms.fuseFD.registerBackingFd(m) } // UnregisterBackingFd unregisters the given ID in the kernel. The ID // should have been acquired before using RegisterBackingFd. func (ms *Server) UnregisterBackingFd(id int32) syscall.Errno { - ms.writeMu.Lock() - _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ms.mountFd), uintptr(_DEV_IOC_BACKING_CLOSE), uintptr(unsafe.Pointer(&id))) - ms.writeMu.Unlock() - - if ms.opts.Debug { - ms.opts.Logger.Printf("ioctl: BACKING_CLOSE id %d: %v", id, errno) - } - return errno + return ms.fuseFD.unregisterBackingFd(id) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go index 9ce018ab..f632b524 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go @@ -103,6 +103,9 @@ var ( {2, "EXCHANGE"}, {4, "WHITEOUT"}, }) + lockFlagNames = newFlagNames([]flagNameEntry{ + {(1 << 0), "FLOCK"}, + }) ) // flagNames associate flag bits to their names. @@ -375,7 +378,7 @@ func (p *_PollIn) string() string { } // Print pretty prints FUSE data types for kernel communication -func Print(obj interface{}) string { +func Print(obj any) string { t, ok := obj.(interface { string() string }) @@ -417,3 +420,15 @@ func (o *IoctlOut) string() string { flagString(ioctlFlagNames, int64(o.Flags), ""), o.InIovs, o.OutIovs) } + +func (l *LkIn) string() string { + return fmt.Sprintf("{Fh %d Owner %d %s %s}", l.Owner, l.Owner, l.Lk.string(), flagString(lockFlagNames, int64(l.LkFlags), "")) +} + +func (lk *FileLock) string() string { + return fmt.Sprintf("{[%d,%d) t%d p%d}", lk.Start, lk.End, lk.Typ, lk.Pid) +} + +func (l *LkOut) string() string { + return l.Lk.string() +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go index 222a032a..9654f391 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go @@ -5,7 +5,6 @@ package fuse import ( - "log" "sync" "syscall" ) @@ -20,8 +19,6 @@ type protocolServer struct { reqInflight []*request connectionDead bool - latencies LatencyMap - kernelSettings InitIn opts *MountOptions @@ -40,14 +37,27 @@ func (ms *protocolServer) handleRequest(h *operationHandler, req *request) { ms.opts.Logger.Println(req.InputDebug()) } - if req.inHeader().NodeId == pollHackInode || + if h == nil || h.Func == nil { + c := req.inHeader().Opcode + if c != _OP_COPY_FILE_RANGE_64 { // _OP_COPY_FILE_RANGE_64 is intentionally not supported. + ms.opts.Logger.Printf("Unimplemented opcode %v", operationName(c)) + } + req.status = ENOSYS + } else if req.inHeader().NodeId == pollHackInode || req.inHeader().NodeId == FUSE_ROOT_ID && h.FileNames > 0 && req.filename() == pollHackName { doPollHackLookup(ms, req) - } else if req.status.Ok() && h.Func == nil { - ms.opts.Logger.Printf("Unimplemented opcode %v", operationName(req.inHeader().Opcode)) - req.status = ENOSYS } else if req.status.Ok() { - h.Func(ms, req) + func() { + defer func() { + if r := recover(); r != nil { + req.status = ms.opts.PanicHandler(r) + if req.status == 0 { + req.status = EIO + } + } + }() + h.Func(ms, req) + }() } // Forget/NotifyReply do not wait for reply from filesystem server. @@ -57,6 +67,8 @@ func (ms *protocolServer) handleRequest(h *operationHandler, req *request) { if req.status.Ok() { req.suppressReply = true } + default: + req.suppressReply = h != nil && h.SuppressReply } if req.status == EINTR { ms.interruptMu.Lock() @@ -145,10 +157,9 @@ func NewProtocolServer(fs RawFileSystem, opts *MountOptions) *ProtocolServer { // ProtocolServer has no pipe, so splicing READ results to the // caller is not possible; force the in-process READ path. optsCopy := *opts + optsCopy.setDefaults(fs) optsCopy.DisableSplice = true - if optsCopy.Logger == nil { - optsCopy.Logger = log.Default() - } + return &ProtocolServer{ protocolServer: protocolServer{ fileSystem: fs, diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/read.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/read.go index 9e9f7980..8421f276 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/read.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/read.go @@ -62,10 +62,7 @@ func (r *readResultFd) Seekable() (fd uintptr, off int64, sz int) { // Reads raw bytes from file descriptor if necessary, using the passed // buffer as storage. func (r *readResultFd) Bytes(buf []byte) ([]byte, Status) { - sz := r.Sz - if len(buf) < sz { - sz = len(buf) - } + sz := min(len(buf), r.Sz) n, err := syscall.Pread(int(r.Fd), buf[:sz], r.Off) if err == io.EOF { diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go index 3ae07cae..43954911 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go @@ -92,11 +92,11 @@ func (r *request) clear() { r.readResult = nil } -func asType(ptr unsafe.Pointer, typ interface{}) interface{} { +func asType(ptr unsafe.Pointer, typ any) any { return reflect.NewAt(reflect.ValueOf(typ).Type(), ptr).Interface() } -func typSize(typ interface{}) uintptr { +func typSize(typ any) uintptr { return reflect.ValueOf(typ).Type().Size() } @@ -216,7 +216,6 @@ func parseRequest(in []byte, kernelSettings *InitIn) (h *operationHandler, inSiz hdr := (*InHeader)(inData) h = getHandler(hdr.Opcode) if h == nil { - log.Printf("Unknown opcode %d", hdr.Opcode) errno = ENOSYS return } @@ -293,7 +292,7 @@ func (r *request) serializeHeader(outPayloadSize int) { // The InitOut structure has 24 bytes (ie. TimeGran and // further fields not available) in fuse version <= 22. // https://john-millikin.com/the-fuse-protocol#FUSE_INIT - if r.inHeader().Opcode == _OP_INIT { + if r.status.Ok() && r.inHeader().Opcode == _OP_INIT { out := (*InitOut)(r.outData()) if out.Minor <= 22 { r.outDataBuf = r.outDataBuf[:24] diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go index ed6ede3f..623f3b5f 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go @@ -13,9 +13,11 @@ import ( "path" "path/filepath" "runtime" + "slices" "strconv" "strings" "sync" + "sync/atomic" "syscall" "time" "unsafe" @@ -45,11 +47,7 @@ type Server struct { // Empty if unmounted. mountPoint string - // writeMu serializes close and notify writes - writeMu sync.Mutex - - // I/O with kernel and daemon. - mountFd int + fuseFD *fuseFD opts *MountOptions @@ -63,14 +61,11 @@ type Server struct { reqPool sync.Pool // Pool for raw requests data - readPool sync.Pool - reqMu sync.Mutex - reqReaders int + readPool sync.Pool singleReader bool canSplice bool - loops sync.WaitGroup - serving bool // for preventing duplicate Serve() calls + serving atomic.Bool // for preventing duplicate Serve() calls // Used to implement WaitMount on macos. ready chan error @@ -96,16 +91,13 @@ func (ms *Server) KernelSettings() *InitIn { const _MAX_NAME_LEN = 20 -// This type may be provided for recording latencies of each FUSE -// operation. +// This type is deprecated. type LatencyMap interface { Add(name string, dt time.Duration) } -// RecordLatencies switches on collection of timing for each request -// coming from the kernel.P assing a nil argument switches off the +// RecordLatencies is deprecated. It is provided for backward compatibility but does not do anything. func (ms *Server) RecordLatencies(l LatencyMap) { - ms.latencies = l } // Unmount calls fusermount -u on the mount. This has the effect of @@ -142,7 +134,7 @@ func (ms *Server) Unmount() (err error) { return } // Wait for event loops to exit. - ms.loops.Wait() + ms.fuseFD.loops.Wait() ms.mountPoint = "" return err } @@ -156,18 +148,7 @@ func alignSlice(buf []byte, alignedByte, blockSize, size uintptr) []byte { return buf[:size] } -// NewServer creates a FUSE server and attaches ("mounts") it to the -// `mountPoint` directory. -// -// See the "Mount styles" section in the package documentation if you want to -// know about the inner workings of the mount process. Usually you do not. -func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server, error) { - if opts == nil { - opts = &MountOptions{ - MaxBackground: _DEFAULT_BACKGROUND_TASKS, - } - } - o := *opts +func (o *MountOptions) setDefaults(fs RawFileSystem) { if o.Logger == nil { o.Logger = log.Default() } @@ -181,17 +162,23 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server if o.MaxWrite > kernelMaxWrite { o.MaxWrite = kernelMaxWrite } + if o.MaxInflightRequestBytes <= 0 { + o.MaxInflightRequestBytes = math.MaxInt + } if o.MaxStackDepth == 0 { o.MaxStackDepth = 1 } if o.Name == "" { name := fs.String() - l := len(name) - if l > _MAX_NAME_LEN { - l = _MAX_NAME_LEN - } + l := min(len(name), _MAX_NAME_LEN) o.Name = strings.Replace(name[:l], ",", ";", -1) } + if o.PanicHandler == nil { + l := o.Logger + o.PanicHandler = func(obj any) Status { + return defaultPanicHandler(l, obj) + } + } for _, s := range []struct { flag bool @@ -205,6 +192,29 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server o.DisabledCapabilities |= s.mask } } +} + +func defaultPanicHandler(logger *log.Logger, obj any) Status { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logger.Printf("panic in FS handler: %v\n%s", obj, buf) + return EIO +} + +// NewServer creates a FUSE server and attaches ("mounts") it to the +// `mountPoint` directory. +// +// See the "Mount styles" section in the package documentation if you want to +// know about the inner workings of the mount process. Usually you do not. +func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server, error) { + if opts == nil { + opts = &MountOptions{ + MaxBackground: _DEFAULT_BACKGROUND_TASKS, + } + } + o := *opts + o.setDefaults(fs) maxReaders := runtime.GOMAXPROCS(0) if maxReaders < minMaxReaders { @@ -213,6 +223,8 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server maxReaders = maxMaxReaders } + readBufSize, readBufBytes, _ := requestAccountingSizes(o.MaxWrite) + ms := &Server{ protocolServer: protocolServer{ fileSystem: fs, @@ -224,26 +236,20 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server singleReader: useSingleReader, ready: make(chan error, 1), } - - ms.protocolServer.writev = ms.writev - ms.reqPool.New = func() interface{} { + ms.reqPool.New = func() any { return &requestAlloc{ request: request{ cancel: make(chan struct{}), }, } } - ms.readPool.New = func() interface{} { - targetSize := o.MaxWrite + int(maxInputSize) - if targetSize < _FUSE_MIN_READ_BUFFER { - targetSize = _FUSE_MIN_READ_BUFFER - } + ms.readPool.New = func() any { // O_DIRECT typically requires buffers aligned to // blocksize (see man 2 open), but requirements vary // across file systems. Presumably, we could also fix // this by reading the requests using readv. - buf := make([]byte, targetSize+logicalBlockSize) - buf = alignSlice(buf, unsafe.Sizeof(WriteIn{}), logicalBlockSize, uintptr(targetSize)) + buf := make([]byte, readBufBytes) + buf = alignSlice(buf, unsafe.Sizeof(WriteIn{}), logicalBlockSize, uintptr(readBufSize)) return buf } mountPoint = filepath.Clean(mountPoint) @@ -260,20 +266,31 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server } ms.mountPoint = mountPoint - ms.mountFd = fd + ms.fuseFD, err = ms.newFuseFD(fd) + if err != nil { + return nil, err + } + ms.protocolServer.writev = ms.fuseFD.writev if code := ms.handleInit(); !code.Ok() { - syscall.Close(fd) + ms.fuseFD.close() // TODO - unmount as well? return nil, fmt.Errorf("init: %s", code) } // This prepares for Serve being called somewhere, either // synchronously or asynchronously. - ms.loops.Add(1) + ms.fuseFD.loops.Add(1) return ms, nil } +func requestAccountingSizes(maxWrite int) (readBufSize, readBufBytes, reqAllocBytes int) { + readBufSize = max(maxWrite+int(maxInputSize), _FUSE_MIN_READ_BUFFER) + readBufBytes = readBufSize + logicalBlockSize + reqAllocBytes = int(unsafe.Sizeof(requestAlloc{})) + return +} + func escape(optionValue string) string { return strings.Replace(strings.Replace(optionValue, `\`, `\\`, -1), `,`, `\,`, -1) } @@ -315,21 +332,16 @@ func (o *MountOptions) optionsStrings() []string { } func (o *MountOptions) containsOption(opt string) bool { - for _, o := range o.Options { - if o == opt { - return true - } - } - return false + return slices.Contains(o.Options, opt) } // DebugData returns internal status information for debugging // purposes. func (ms *Server) DebugData() string { var r int - ms.reqMu.Lock() - r = ms.reqReaders - ms.reqMu.Unlock() + ms.fuseFD.reqMu.Lock() + r = ms.fuseFD.reqReaders + ms.fuseFD.reqMu.Unlock() return fmt.Sprintf("readers: %d", r) } @@ -353,115 +365,23 @@ func handleEINTR(fn func() error) (err error) { return } -// Returns a new request, or error. Returns -// nil, OK if we have too many readers already. -func (ms *Server) readRequest() (req *requestAlloc, code Status) { - ms.reqMu.Lock() - if ms.reqReaders > ms.maxReaders { - ms.reqMu.Unlock() - return nil, OK - } - ms.reqReaders++ - ms.reqMu.Unlock() - - reqIface := ms.reqPool.Get() - req = reqIface.(*requestAlloc) - destIface := ms.readPool.Get() - dest := destIface.([]byte) - - var n int - err := handleEINTR(func() error { - var err error - n, err = syscall.Read(ms.mountFd, dest) - return err - }) - if err != nil { - code = ToStatus(err) - ms.reqPool.Put(reqIface) - ms.reqMu.Lock() - ms.reqReaders-- - ms.reqMu.Unlock() - return nil, code - } - - if ms.latencies != nil { - req.startTime = time.Now() - } - ms.reqMu.Lock() - defer ms.reqMu.Unlock() - gobbled := req.setInput(dest[:n]) - if len(req.inputBuf) < int(unsafe.Sizeof(InHeader{})) { - log.Printf("Short read for input header: %v", req.inputBuf) - return nil, EINVAL - } - opCode := ((*InHeader)(unsafe.Pointer(&req.inputBuf[0]))).Opcode - /* These messages don't expect reply, so they cost nothing for - the kernel to send. Make sure we're not overwhelmed by not - spawning a new reader. - */ - needsBackPressure := (opCode == _OP_FORGET || opCode == _OP_BATCH_FORGET) - - if !gobbled { - ms.readPool.Put(destIface) - } - ms.reqReaders-- - if !ms.singleReader && ms.reqReaders <= 0 && !needsBackPressure { - ms.loops.Add(1) - go ms.loop() - } - - return req, OK -} - -// returnRequest returns a request to the pool of unused requests. -func (ms *Server) returnRequest(req *requestAlloc) { - ms.recordStats(&req.request) - - if req.bufferPoolOutputBuf != nil { - ms.buffers.FreeBuffer(req.bufferPoolOutputBuf) - req.bufferPoolOutputBuf = nil - } - if req.interrupted { - req.interrupted = false - req.cancel = make(chan struct{}, 0) - } - req.clear() - - if p := req.bufferPoolInputBuf; p != nil { - req.bufferPoolInputBuf = nil - ms.readPool.Put(p) - } - ms.reqPool.Put(req) -} - -func (ms *Server) recordStats(req *request) { - if ms.latencies != nil { - dt := time.Now().Sub(req.startTime) - opname := operationName(req.inHeader().Opcode) - ms.latencies.Add(opname, dt) - } -} - // Serve initiates the FUSE loop. Normally, callers should run Serve() // and wait for it to exit, but tests will want to run this in a // goroutine. // // Each filesystem operation executes in a separate goroutine. func (ms *Server) Serve() { - if ms.serving { + if !ms.serving.CompareAndSwap(false, true) { // Calling Serve() multiple times leads to a panic on unmount and fun // debugging sessions ( https://github.com/hanwen/go-fuse/issues/512 ). // Catch it early. log.Panic("Serve() must only be called once, you have called it a second time") } - ms.serving = true ms.loop() - ms.loops.Wait() + ms.fuseFD.loops.Wait() - ms.writeMu.Lock() - syscall.Close(ms.mountFd) - ms.writeMu.Unlock() + ms.fuseFD.close() // shutdown in-flight cache retrieves. // @@ -486,7 +406,7 @@ func (ms *Server) Serve() { // Wait waits for the serve loop to exit. This should only be called // after Serve has been called, or it will hang indefinitely. func (ms *Server) Wait() { - ms.loops.Wait() + ms.fuseFD.loops.Wait() } func (ms *Server) handleInit() Status { @@ -494,7 +414,7 @@ func (ms *Server) handleInit() Status { // and don't spawn new readers. orig := ms.singleReader ms.singleReader = true - req, errNo := ms.readRequest() + req, errNo := ms.fuseFD.readRequest() ms.singleReader = orig if errNo != OK || req == nil { @@ -542,10 +462,10 @@ func (ms *Server) handleInit() Status { // BenchmarkGoFuseReaddir 4074 361568 ns/op // BenchmarkGoFuseReaddir-2 3511 319765 ns/op func (ms *Server) loop() { - defer ms.loops.Done() + defer ms.fuseFD.loops.Done() exit: for { - req, errNo := ms.readRequest() + req, errNo := ms.fuseFD.readRequest() switch errNo { case OK: if req == nil { @@ -567,28 +487,26 @@ exit: break exit } - if ms.singleReader { + if ms.singleReader && ms.fuseFD.canAcceptAnother() { go ms.handleRequest(req) - } else { - ms.handleRequest(req) + continue } + ms.handleRequest(req) } } func (ms *Server) handleRequest(req *requestAlloc) Status { - defer ms.returnRequest(req) + defer ms.fuseFD.returnRequest(req) if ms.opts.SingleThreaded { ms.requestProcessingMu.Lock() defer ms.requestProcessingMu.Unlock() } h, inSize, outSize, outPayloadSize, code := parseRequest(req.inputBuf, &ms.kernelSettings) - if !code.Ok() { + req.request.status = code + if !code.Ok() && code != ENOSYS { ms.opts.Logger.Printf("parseRequest: %v", code) - return code } - - req.suppressReply = h.SuppressReply req.inPayload = req.inputBuf[inSize:] req.inputBuf = req.inputBuf[:inSize] req.outHeaderBuf = req.outHeaderInline[:] @@ -603,7 +521,7 @@ func (ms *Server) handleRequest(req *requestAlloc) Status { if req.suppressReply { return OK } - errno := ms.write(&req.request) + errno := ms.fuseFD.write(&req.request) if errno != 0 { // Ignore ENOENT for INTERRUPT responses which // indicates that the referred request is no longer @@ -643,24 +561,6 @@ func notifyWrite(writev func([][]byte) (int, syscall.Errno), opts *MountOptions, return Status(errno) } -func (ms *Server) writev(iov [][]byte) (int, syscall.Errno) { - // Protect against concurrent close. - ms.writeMu.Lock() - defer ms.writeMu.Unlock() - n, err := writev(ms.mountFd, iov) - - var errno syscall.Errno - if err != nil { - errno = err.(syscall.Errno) - if errno == syscall.EINVAL { - // Detail: the kernel returns EINVAL for unsupported - // notify methods. - errno = syscall.ENOSYS - } - } - return n, errno -} - func (ms *protocolServer) notifyWrite(req *request) Status { if ms.writev == nil { return ENOSYS @@ -724,13 +624,11 @@ func (ms *Server) PruneNotify(nodes []uint64) Status { // region, it gives updated data directly to the kernel. func (ms *protocolServer) InodeNotifyStoreCache(node uint64, offset int64, data []byte) Status { for len(data) > 0 { - size := len(data) - if size > math.MaxInt32 { + size := min(len(data), // NotifyStoreOut has only uint32 for size. // we check for max(int32), not max(uint32), because on 32-bit // platforms int has only 31-bit for positive range. - size = math.MaxInt32 - } + math.MaxInt32) st := ms.inodeNotifyStoreCache32(node, offset, data[:size]) if st != OK { @@ -774,10 +672,7 @@ func (ms *protocolServer) InodeRetrieveCache(node uint64, offset int64, dest []b // TODO spawn some number of readahead retrievers in parallel. ntotal := 0 for { - chunkSize := len(dest) - if chunkSize > ms.opts.MaxWrite { - chunkSize = ms.opts.MaxWrite - } + chunkSize := min(len(dest), ms.opts.MaxWrite) n, st = ms.inodeRetrieveCache1(node, offset, dest[:chunkSize]) if st != OK || n == 0 { break @@ -806,10 +701,7 @@ func (ms *protocolServer) inodeRetrieveCache1(node uint64, offset int64, dest [] // // ( InodeRetrieveCache calls us with chunks not larger than // ms.opts.MaxWrite, but MaxWrite is int, so let's be extra cautious ) - size := len(dest) - if size > math.MaxInt32 { - size = math.MaxInt32 - } + size := min(len(dest), math.MaxInt32) dest = dest[:size] q := (*NotifyRetrieveOut)(req.outData()) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_linux.go index 2feaaeb5..ebf1c5c5 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_linux.go @@ -30,7 +30,7 @@ func (s *Server) setSplice() { // If a short read occurs (payloadLen < fdData.Sz), the header in the pipe // would carry the wrong total length, so we return an error and let the // caller fall back to a Pread-based path. -func (ms *Server) trySplice(req *request, readResult ReadResult) error { +func (r *fuseFD) trySplice(req *request, readResult ReadResult) error { // The caller (handleRequest) already called req.serializeHeader with // readResult.Size(), so req.outHeaderBuf is correct for the optimistic case. total := len(req.outHeaderBuf) + len(req.outDataBuf) + readResult.Size() @@ -84,17 +84,21 @@ func (ms *Server) trySplice(req *request, readResult ReadResult) error { return fmt.Errorf("fallback drain: %w", err) } - if ms.opts.Debug { + if r.server.opts.Debug { log.Printf("tx %d: OK fixup fd %db data", req.inHeader().Unique, payloadLen) } // New length. req.serializeHeader(payloadLen) - return ms.trySplice(req, ReadResultPipe(pair, payloadLen)) + return r.trySplice(req, ReadResultPipe(pair, payloadLen)) } // Write header + payload to /dev/fuse. - _, err = pair.WriteTo(uintptr(ms.mountFd), total) + if cerr := r.withFD(func(fd int) { + _, err = pair.WriteTo(uintptr(fd), total) + }); cerr != nil { + return cerr + } return err } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_unix.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_unix.go index ef8a6bd1..4d09d0f3 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_unix.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_unix.go @@ -3,7 +3,6 @@ package fuse import ( - "os" "syscall" "unsafe" ) @@ -35,13 +34,10 @@ func writev(fd int, packet [][]byte) (n int, err error) { iovecs = append(iovecs, vec) } - sysErr := handleEINTR(func() error { + err = handleEINTR(func() error { var err error n, err = sys_writev(fd, &iovecs[0], len(iovecs)) return err }) - if sysErr != nil { - err = os.NewSyscallError("writev", sysErr) - } return n, err } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go index 2823fccf..94716db8 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go @@ -457,6 +457,8 @@ type FileLock struct { Pid uint32 } +const LK_FLOCK = (1 << 0) + type LkIn struct { InHeader Fh uint64 diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/types_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/types_darwin.go index 59acd59c..f92675a3 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/types_darwin.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/types_darwin.go @@ -106,6 +106,12 @@ type ExchangeIn struct { Options uint64 } +type MonitorIn struct { + InHeader + Flags uint32 + Padding uint32 +} + func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) { s.Blocks = statfs.Blocks s.Bfree = statfs.Bfree diff --git a/vendor/github.com/hanwen/go-fuse/v2/internal/access.go b/vendor/github.com/hanwen/go-fuse/v2/internal/access.go index 1d865070..598924b3 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/internal/access.go +++ b/vendor/github.com/hanwen/go-fuse/v2/internal/access.go @@ -6,41 +6,45 @@ package internal import ( "os/user" + "slices" "strconv" + "syscall" ) // HasAccess tests if a caller can access a file with permissions -// `perm` in mode `mask` +// `perm` in mode `mask`. This follows Unix semantics: the caller is +// classified as owner, group member or other, and only the +// permission bits of that single class are consulted. All bits in +// mask must be granted. func HasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask uint32) bool { - if callerUid == 0 { - // root can do anything. - return true - } - mask = mask & 7 + mask &= 7 if mask == 0 { return true } - - if callerUid == fileUid { - if perm&(mask<<6) != 0 { - return true - } - } - if callerGid == fileGid { - if perm&(mask<<3) != 0 { - return true - } - } - if perm&mask != 0 { - return true + if callerUid == 0 { + // Root can read and write anything, but for execute, at + // least one of the execute bits must be set, unless it is a + // directory. + return mask&1 == 0 || perm&0111 != 0 || perm&syscall.S_IFMT == syscall.S_IFDIR } - // Check other groups. - if perm&(mask<<3) == 0 { - // avoid expensive lookup if it's not allowed anyway - return false - } + var bits uint32 + switch { + case callerUid == fileUid: + bits = perm >> 6 + case callerGid == fileGid: + bits = perm >> 3 + case (perm>>3)&mask != perm&mask && isSupplementaryGroup(callerUid, fileGid): + // The expensive group lookup only matters if group and other + // bits differ for the requested mask. + bits = perm >> 3 + default: + bits = perm + } + return bits&mask == mask +} +func isSupplementaryGroup(callerUid, fileGid uint32) bool { u, err := user.LookupId(strconv.Itoa(int(callerUid))) if err != nil { return false @@ -49,12 +53,5 @@ func HasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask if err != nil { return false } - - fileGidStr := strconv.Itoa(int(fileGid)) - for _, gidStr := range gs { - if gidStr == fileGidStr { - return true - } - } - return false + return slices.Contains(gs, strconv.Itoa(int(fileGid))) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/internal/ioctl/ioctl.go b/vendor/github.com/hanwen/go-fuse/v2/internal/ioctl/ioctl.go index fd813782..424e9489 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/internal/ioctl/ioctl.go +++ b/vendor/github.com/hanwen/go-fuse/v2/internal/ioctl/ioctl.go @@ -2,10 +2,13 @@ package ioctl // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/ioctl.h +// Direction bits, in kernel convention (_IOC_NONE, _IOC_WRITE, +// _IOC_READ): the direction is seen from userspace, so READ means the +// driver writes data back to the caller. const ( NONE = 0x0 - READ = 0x1 - WRITE = 0x2 + WRITE = 0x1 + READ = 0x2 ) // The ioctl command. It encodes direction (read/write), argument size @@ -24,12 +27,14 @@ func New(dir byte, typ byte, nr byte, size uintptr) Command { Command(nr) } -// Read returns true if the ioctl reads data +// Read returns true if the caller reads data, ie. the driver fills +// the output buffer. func (c Command) Read() bool { return (c>>(14+16))&READ != 0 } -// Write returns true if the ioctl writes data +// Write returns true if the caller writes data, ie. the driver +// receives the input buffer. func (c Command) Write() bool { return (c>>(14+16))&WRITE != 0 } diff --git a/vendor/github.com/hanwen/go-fuse/v2/splice/splice.go b/vendor/github.com/hanwen/go-fuse/v2/splice/splice.go index 9ec14b5f..c12b16e2 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/splice/splice.go +++ b/vendor/github.com/hanwen/go-fuse/v2/splice/splice.go @@ -94,7 +94,7 @@ func newSplicePair() (p *Pair, err error) { } var errNo syscall.Errno p.size, errNo = fcntl(uintptr(p.r), F_GETPIPE_SZ, 0) - if err == syscall.EINVAL { + if errNo == syscall.EINVAL { p.size = DefaultPipeSize return p, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 528d3d79..f7ced036 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -15,7 +15,7 @@ github.com/dustin/go-humanize # github.com/google/uuid v1.6.0 ## explicit github.com/google/uuid -# github.com/hanwen/go-fuse/v2 v2.10.1 +# github.com/hanwen/go-fuse/v2 v2.11.0 ## explicit; go 1.21 github.com/hanwen/go-fuse/v2/fs github.com/hanwen/go-fuse/v2/fuse