kubo/fuse/mount/mount.go
Juan Batiz-Benet 58f39687cf logs: removed all log.Errors unhelpful to users
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.
2015-02-03 01:06:07 -08:00

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())
}