mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-24 11:57:44 +08:00
Let's save log.Error for things the user can take action on. Moved all our diagnostics to log.Debug. We can ideally reduce them even further.
195 lines
4.4 KiB
Go
195 lines
4.4 KiB
Go
// package mount provides a simple abstraction around a mount point
|
|
package mount
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"runtime"
|
|
"time"
|
|
|
|
fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
|
|
fs "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
|
|
ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
|
|
|
|
u "github.com/jbenet/go-ipfs/util"
|
|
)
|
|
|
|
var log = u.Logger("mount")
|
|
|
|
var MountTimeout = time.Second * 5
|
|
|
|
// Mount represents a filesystem mount
|
|
type Mount interface {
|
|
// MountPoint is the path at which this mount is mounted
|
|
MountPoint() string
|
|
|
|
// Unmounts the mount
|
|
Unmount() error
|
|
|
|
// CtxGroup returns the mount's CtxGroup to be able to link it
|
|
// to other processes. Unmount upon closing.
|
|
CtxGroup() ctxgroup.ContextGroup
|
|
}
|
|
|
|
// mount implements go-ipfs/fuse/mount
|
|
type mount struct {
|
|
mpoint string
|
|
filesys fs.FS
|
|
fuseConn *fuse.Conn
|
|
// closeErr error
|
|
|
|
cg ctxgroup.ContextGroup
|
|
}
|
|
|
|
// Mount mounts a fuse fs.FS at a given location, and returns a Mount instance.
|
|
// parent is a ContextGroup to bind the mount's ContextGroup to.
|
|
func NewMount(p ctxgroup.ContextGroup, fsys fs.FS, mountpoint string) (Mount, error) {
|
|
conn, err := fuse.Mount(mountpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
m := &mount{
|
|
mpoint: mountpoint,
|
|
fuseConn: conn,
|
|
filesys: fsys,
|
|
cg: ctxgroup.WithParent(p), // link it to parent.
|
|
}
|
|
m.cg.SetTeardown(m.unmount)
|
|
|
|
// 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() {
|
|
err := fs.Serve(m.fuseConn, m.filesys)
|
|
log.Debugf("Mounting %s -- fs.Serve returned (%s)", err)
|
|
errs <- err
|
|
close(errs)
|
|
}()
|
|
|
|
// 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
|
|
}
|
|
|
|
log.Infof("Mounted %s", m.MountPoint())
|
|
return nil
|
|
}
|
|
|
|
// umount 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 {
|
|
return nil
|
|
}
|
|
log.Debug("fuse unmount err: %s", err)
|
|
|
|
// try closing the fuseConn
|
|
err = m.fuseConn.Close()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
log.Debug("fuse conn error: %s", err)
|
|
}
|
|
|
|
// try mount.ForceUnmountManyTimes
|
|
if err := ForceUnmountManyTimes(m, 10); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Seemingly unmounted %s", m.MountPoint())
|
|
return nil
|
|
}
|
|
|
|
func (m *mount) CtxGroup() ctxgroup.ContextGroup {
|
|
return m.cg
|
|
}
|
|
|
|
func (m *mount) MountPoint() string {
|
|
return m.mpoint
|
|
}
|
|
|
|
func (m *mount) Unmount() error {
|
|
// call ContextCloser Close(), which calls unmount() exactly once.
|
|
return m.cg.Close()
|
|
}
|
|
|
|
// ForceUnmount attempts to forcibly unmount a given mount.
|
|
// It does so by calling diskutil or fusermount directly.
|
|
func ForceUnmount(m Mount) error {
|
|
point := m.MountPoint()
|
|
log.Infof("Force-Unmounting %s...", point)
|
|
|
|
var cmd *exec.Cmd
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
cmd = exec.Command("diskutil", "umount", "force", point)
|
|
case "linux":
|
|
cmd = exec.Command("fusermount", "-u", point)
|
|
default:
|
|
return fmt.Errorf("unmount: unimplemented")
|
|
}
|
|
|
|
errc := make(chan error, 1)
|
|
go func() {
|
|
defer close(errc)
|
|
|
|
// try vanilla unmount first.
|
|
if err := exec.Command("umount", point).Run(); err == nil {
|
|
return
|
|
}
|
|
|
|
// retry to unmount with the fallback cmd
|
|
errc <- cmd.Run()
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(2 * time.Second):
|
|
return fmt.Errorf("umount timeout")
|
|
case err := <-errc:
|
|
return err
|
|
}
|
|
}
|
|
|
|
// ForceUnmountManyTimes attempts to forcibly unmount a given mount,
|
|
// many times. It does so by calling diskutil or fusermount directly.
|
|
// Attempts a given number of times.
|
|
func ForceUnmountManyTimes(m Mount, attempts int) error {
|
|
var err error
|
|
for i := 0; i < attempts; i++ {
|
|
err = ForceUnmount(m)
|
|
if err == nil {
|
|
return err
|
|
}
|
|
|
|
<-time.After(time.Millisecond * 500)
|
|
}
|
|
return fmt.Errorf("Unmount %s failed after 10 seconds of trying.", m.MountPoint())
|
|
}
|