| 1 | // FUSE mount/unmount lifecycle. go-fuse only builds on linux, darwin, and freebsd. |
| 2 | //go:build (linux || darwin || freebsd) && !nofuse |
| 3 | |
| 4 | package mount |
| 5 | |
| 6 | import ( |
| 7 | "errors" |
| 8 | "fmt" |
| 9 | "sync" |
| 10 | |
| 11 | "github.com/hanwen/go-fuse/v2/fs" |
| 12 | "github.com/hanwen/go-fuse/v2/fuse" |
| 13 | ) |
| 14 | |
| 15 | var ErrNotMounted = errors.New("not mounted") |
| 16 | |
| 17 | // mount implements go-ipfs/fuse/mount. |
| 18 | type mount struct { |
| 19 | mpoint string |
| 20 | server *fuse.Server |
| 21 | |
| 22 | active bool |
| 23 | activeLock *sync.RWMutex |
| 24 | |
| 25 | unmountOnce sync.Once |
| 26 | } |
| 27 | |
| 28 | // NewMount mounts a FUSE filesystem at a given location, and returns a Mount instance. |
| 29 | func NewMount(root fs.InodeEmbedder, mountpoint string, opts *fs.Options) (Mount, error) { |
| 30 | PlatformMountOpts(&opts.MountOptions) |
| 31 | server, err := fs.Mount(mountpoint, root, opts) |
| 32 | if err != nil { |
| 33 | return nil, fmt.Errorf("mounting %s: %w", mountpoint, err) |
| 34 | } |
| 35 | |
| 36 | m := &mount{ |
| 37 | mpoint: mountpoint, |
| 38 | server: server, |
| 39 | active: true, |
| 40 | activeLock: &sync.RWMutex{}, |
| 41 | } |
| 42 | |
| 43 | // Detect external unmount (e.g. fusermount -u) so IsActive |
| 44 | // returns false and Unmount returns ErrNotMounted. |
| 45 | go func() { |
| 46 | server.Wait() |
| 47 | m.setActive(false) |
| 48 | }() |
| 49 | |
| 50 | log.Infof("Mounted %s", mountpoint) |
| 51 | return m, nil |
| 52 | } |
| 53 | |
| 54 | // unmount is called exactly once to unmount this service. |
| 55 | func (m *mount) unmount() error { |
| 56 | log.Infof("Unmounting %s", m.MountPoint()) |
| 57 | |
| 58 | err := m.server.Unmount() |
| 59 | if err == nil { |
| 60 | m.setActive(false) |
| 61 | return nil |
| 62 | } |
| 63 | log.Warnf("fuse unmount err: %s", err) |
| 64 | |
| 65 | // try mount.ForceUnmountManyTimes |
| 66 | if err := ForceUnmountManyTimes(m, 10); err != nil { |
| 67 | return err |
| 68 | } |
| 69 | |
| 70 | log.Infof("Seemingly unmounted %s", m.MountPoint()) |
| 71 | m.setActive(false) |
| 72 | return nil |
| 73 | } |
| 74 | |
| 75 | func (m *mount) MountPoint() string { |
| 76 | return m.mpoint |
| 77 | } |
| 78 | |
| 79 | func (m *mount) Unmount() error { |
| 80 | if !m.IsActive() { |
| 81 | return ErrNotMounted |
| 82 | } |
| 83 | |
| 84 | var err error |
| 85 | m.unmountOnce.Do(func() { |
| 86 | err = m.unmount() |
| 87 | }) |
| 88 | |
| 89 | return err |
| 90 | } |
| 91 | |
| 92 | func (m *mount) IsActive() bool { |
| 93 | m.activeLock.RLock() |
| 94 | defer m.activeLock.RUnlock() |
| 95 | |
| 96 | return m.active |
| 97 | } |
| 98 | |
| 99 | func (m *mount) setActive(a bool) { |
| 100 | m.activeLock.Lock() |
| 101 | m.active = a |
| 102 | m.activeLock.Unlock() |
| 103 | } |