kubo/fuse/mount/fuse.go
Andrew Gillis 90b73d2ad2
refactor: remove goprocess (#10872)
* refactor: remove goprocess

The `goprocess` package is no longer needed. It can be replaces by modern `context` and `context.AfterFunc`.

* mod tidy

* log unmount errors on shutdown

* Do not log non-mounted errors on shutdown

* Use WaitGroup associated with IPFS node to wait for services to whutdown

* Prefer explicit Close to context.ArterFunc

* Do not use node-level WaitGroup

* Unmount for non-supported platforms

* fix return values

* test: daemon shuts down gracefully

make sure ongoing operations dont block shutdown

* test(cli): add TestFUSE

* test: smarter RequiresFUSE

opportunistically run FUSE tests if env has fusermount
and TEST_FUSE was not explicitly set

* docs: changelog

---------

Co-authored-by: gammazero <gammazero@users.noreply.github.com>
Co-authored-by: Marcin Rataj <lidel@lidel.org>
2025-08-06 00:33:45 +02:00

164 lines
3.2 KiB
Go

//go:build !nofuse && !windows && !openbsd && !netbsd && !plan9
// +build !nofuse,!windows,!openbsd,!netbsd,!plan9
package mount
import (
"errors"
"fmt"
"sync"
"time"
"bazil.org/fuse"
"bazil.org/fuse/fs"
)
var ErrNotMounted = errors.New("not mounted")
// mount implements go-ipfs/fuse/mount.
type mount struct {
mpoint string
filesys fs.FS
fuseConn *fuse.Conn
active bool
activeLock *sync.RWMutex
unmountOnce sync.Once
}
// Mount mounts a fuse fs.FS at a given location, and returns a Mount instance.
// ctx is parent is a ContextGroup to bind the mount's ContextGroup to.
func NewMount(fsys fs.FS, mountpoint string, allowOther bool) (Mount, error) {
var conn *fuse.Conn
var err error
mountOpts := []fuse.MountOption{
fuse.MaxReadahead(64 * 1024 * 1024),
fuse.AsyncRead(),
}
if allowOther {
mountOpts = append(mountOpts, fuse.AllowOther())
}
conn, err = fuse.Mount(mountpoint, mountOpts...)
if err != nil {
return nil, err
}
m := &mount{
mpoint: mountpoint,
fuseConn: conn,
filesys: fsys,
active: false,
activeLock: &sync.RWMutex{},
}
// launch the mounting process.
if err = m.mount(); err != nil {
_ = m.Unmount() // just in case.
return nil, err
}
return m, nil
}
func (m *mount) mount() error {
log.Infof("Mounting %s", m.MountPoint())
errs := make(chan error, 1)
go func() {
// fs.Serve blocks until the filesystem is unmounted.
err := fs.Serve(m.fuseConn, m.filesys)
log.Debugf("%s is unmounted", m.MountPoint())
if err != nil {
log.Debugf("fs.Serve returned (%s)", err)
errs <- err
}
m.setActive(false)
}()
// wait for the mount process to be done, or timed out.
select {
case <-time.After(MountTimeout):
return fmt.Errorf("mounting %s timed out", m.MountPoint())
case err := <-errs:
return err
case <-m.fuseConn.Ready:
}
// check if the mount process has an error to report
if err := m.fuseConn.MountError; err != nil {
return err
}
m.setActive(true)
log.Infof("Mounted %s", m.MountPoint())
return nil
}
// unmount is called exactly once to unmount this service.
// note that closing the connection will not always unmount
// properly. If that happens, we bring out the big guns
// (mount.ForceUnmountManyTimes, exec unmount).
func (m *mount) unmount() error {
log.Infof("Unmounting %s", m.MountPoint())
// try unmounting with fuse lib
err := fuse.Unmount(m.MountPoint())
if err == nil {
m.setActive(false)
return nil
}
log.Warnf("fuse unmount err: %s", err)
// try closing the fuseConn
err = m.fuseConn.Close()
if err == nil {
m.setActive(false)
return nil
}
log.Warnf("fuse conn error: %s", err)
// try mount.ForceUnmountManyTimes
if err := ForceUnmountManyTimes(m, 10); err != nil {
return err
}
log.Infof("Seemingly unmounted %s", m.MountPoint())
m.setActive(false)
return nil
}
func (m *mount) MountPoint() string {
return m.mpoint
}
func (m *mount) Unmount() error {
if !m.IsActive() {
return ErrNotMounted
}
var err error
m.unmountOnce.Do(func() {
err = m.unmount()
})
return err
}
func (m *mount) IsActive() bool {
m.activeLock.RLock()
defer m.activeLock.RUnlock()
return m.active
}
func (m *mount) setActive(a bool) {
m.activeLock.Lock()
m.active = a
m.activeLock.Unlock()
}