@cryptotaxi247 / kubo / commits / e21b1f662

fuse mount lifecycle fixes

This commit cleans up how mounting was being done. It now successfully signals when it is properly mounted and listen to close signals correctly.

Juan Batiz-Benet committed Jan 2, 2015 at 07:57 UTC e21b1f662b6d0d060e18fca62c50d0c79e35341b
8 files changed +207 -253
cmd/ipfs/daemon.go
+2
@@ -134,6 +134,8 @@ func daemonFunc(req cmds.Request) (interface{}, error) {
134 if err != nil {
135 return nil, err
136 }
137 + fmt.Printf("IPFS mounted at: %s\n", fsdir)
138 + fmt.Printf("IPNS mounted at: %s\n", nsdir)
139 }
140
141 return nil, listenAndServeAPI(node, req, apiMaddr)
core/commands/mount_unix.go
+35 -29
@@ -143,6 +143,33 @@ baz
143 },
144 }
145
146 +func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
147 + // check if we already have live mounts.
148 + // if the user said "Mount", then there must be something wrong.
149 + // so, close them and try again.
150 + if node.Mounts.Ipfs != nil {
151 + node.Mounts.Ipfs.Unmount()
152 + }
153 + if node.Mounts.Ipns != nil {
154 + node.Mounts.Ipns.Unmount()
155 + }
156 +
157 + if err := platformFuseChecks(); err != nil {
158 + return err
159 + }
160 +
161 + var err error
162 + if err = doMount(node, fsdir, nsdir); err != nil {
163 + return err
164 + }
165 +
166 + return nil
167 +}
168 +
169 +var platformFuseChecks = func() error {
170 + return nil
171 +}
172 +
173 func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
174 fmtFuseErr := func(err error) error {
175 s := err.Error()
@@ -176,8 +203,14 @@ func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
203 <-done
204
205 if err1 != nil || err2 != nil {
179 - fsmount.Close()
180 - nsmount.Close()
206 + log.Infof("error mounting: %s %s", err1, err2)
207 + if fsmount != nil {
208 + fsmount.Unmount()
209 + }
210 + if nsmount != nil {
211 + nsmount.Unmount()
212 + }
213 +
214 if err1 != nil {
215 return fmtFuseErr(err1)
216 } else {
@@ -190,30 +223,3 @@ func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
223 node.Mounts.Ipns = nsmount
224 return nil
225 }
193 -
194 -var platformFuseChecks = func() error {
195 - return nil
196 -}
197 -
198 -func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
199 - // check if we already have live mounts.
200 - // if the user said "Mount", then there must be something wrong.
201 - // so, close them and try again.
202 - if node.Mounts.Ipfs != nil {
203 - node.Mounts.Ipfs.Unmount()
204 - }
205 - if node.Mounts.Ipns != nil {
206 - node.Mounts.Ipns.Unmount()
207 - }
208 -
209 - if err := platformFuseChecks(); err != nil {
210 - return err
211 - }
212 -
213 - var err error
214 - if err = doMount(node, fsdir, nsdir); err != nil {
215 - return err
216 - }
217 -
218 - return nil
219 -}
fuse/ipns/ipns_test.go
+1 -1
@@ -69,7 +69,7 @@ func setupIpnsTest(t *testing.T, node *core.IpfsNode) (*core.IpfsNode, *fstest.M
69 }
70 }
71
72 - fs, err := NewIpns(node, node.PrivateKey, "")
72 + fs, err := NewFileSystem(node, node.PrivateKey, "")
73 if err != nil {
74 t.Fatal(err)
75 }
fuse/ipns/ipns_unix.go
+1 -1
@@ -37,7 +37,7 @@ type FileSystem struct {
37 }
38
39 // NewFileSystem constructs new fs using given core.IpfsNode instance.
40 -func NewIpns(ipfs *core.IpfsNode, sk ci.PrivKey, ipfspath string) (*FileSystem, error) {
40 +func NewFileSystem(ipfs *core.IpfsNode, sk ci.PrivKey, ipfspath string) (*FileSystem, error) {
41 root, err := CreateRoot(ipfs, []ci.PrivKey{sk}, ipfspath)
42 if err != nil {
43 return nil, err
fuse/ipns/mount_unix.go
+7 -89
@@ -1,100 +1,18 @@
1 +// +build linux darwin freebsd
2 +
3 package ipns
4
5 import (
4 - "fmt"
5 - "os/exec"
6 - "runtime"
7 - "time"
8 -
9 - fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
10 - fs "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
11 -
6 core "github.com/jbenet/go-ipfs/core"
7 mount "github.com/jbenet/go-ipfs/fuse/mount"
8 )
9
16 -// Mount mounts an IpfsNode instance at a particular path. It
17 -// serves until the process receives exit signals (to Unmount).
18 -func Mount(ipfs *core.IpfsNode, fpath string, ipfspath string) (mount.Mount, error) {
19 - log.Infof("Mounting ipns at %s...", fpath)
20 -
21 - // setup the Mount abstraction.
22 - m := mount.New(ipfs.Context(), fpath)
23 -
24 - // go serve the mount
25 - m.Mount(func(m mount.Mount) error {
26 - return internalMount(ipfs, fpath, ipfspath)
27 - }, internalUnmount)
28 -
29 - select {
30 - case <-m.Closed():
31 - return nil, fmt.Errorf("failed to mount")
32 - case <-time.After(time.Second):
33 - // assume it worked...
34 - }
35 -
36 - // bind the mount (ContextGroup) to the node, so that when the node exits
37 - // the fsclosers are automatically closed.
38 - ipfs.AddChildGroup(m)
39 - return m, nil
40 -}
41 -
42 -// mount attempts to mount at the provided FUSE mount point
43 -func internalMount(ipfs *core.IpfsNode, fpath string, ipfspath string) error {
44 -
45 - c, err := fuse.Mount(fpath)
46 - if err != nil {
47 - return err
48 - }
49 - defer c.Close()
50 -
51 - fsys, err := NewIpns(ipfs, ipfs.PrivateKey, ipfspath)
10 +// Mount mounts ipns at a given location, and returns a mount.Mount instance.
11 +func Mount(ipfs *core.IpfsNode, ipnsmp, ipfsmp string) (mount.Mount, error) {
12 + fsys, err := NewFileSystem(ipfs, ipfs.PrivateKey, ipfsmp)
13 if err != nil {
53 - return err
54 - }
55 -
56 - log.Infof("Mounted ipns at %s.", fpath)
57 - if err := fs.Serve(c, fsys); err != nil {
58 - return err
59 - }
60 -
61 - // check if the mount process has an error to report
62 - <-c.Ready
63 - if err := c.MountError; err != nil {
64 - return err
14 + return nil, err
15 }
66 - return nil
67 -}
68 -
69 -// unmount attempts to unmount the provided FUSE mount point, forcibly
70 -// if necessary.
71 -func internalUnmount(m mount.Mount) error {
72 - point := m.MountPoint()
73 - log.Infof("Unmounting ipns at %s...", point)
74 -
75 - var cmd *exec.Cmd
76 - switch runtime.GOOS {
77 - case "darwin":
78 - cmd = exec.Command("diskutil", "umount", "force", point)
79 - case "linux":
80 - cmd = exec.Command("fusermount", "-u", point)
81 - default:
82 - return fmt.Errorf("unmount: unimplemented")
83 - }
84 -
85 - errc := make(chan error, 1)
86 - go func() {
87 - if err := exec.Command("umount", point).Run(); err == nil {
88 - errc <- err
89 - }
90 - // retry to unmount with the fallback cmd
91 - errc <- cmd.Run()
92 - }()
16
94 - select {
95 - case <-time.After(1 * time.Second):
96 - return fmt.Errorf("umount timeout")
97 - case err := <-errc:
98 - return err
99 - }
17 + return mount.NewMount(ipfs, fsys, ipnsmp)
18 }
fuse/mount/mount.go
+147 -45
@@ -3,9 +3,12 @@ package mount
3
4 import (
5 "fmt"
6 + "os/exec"
7 + "runtime"
8 "time"
9
8 - context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
10 + fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
11 + fs "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
12 ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
13
14 u "github.com/jbenet/go-ipfs/util"
@@ -13,62 +16,119 @@ import (
16
17 var log = u.Logger("mount")
18
19 +var MountTimeout = time.Second * 5
20 +
21 // Mount represents a filesystem mount
22 type Mount interface {
18 -
23 // MountPoint is the path at which this mount is mounted
24 MountPoint() string
25
22 - // Mount function sets up a mount + registers the unmount func
23 - Mount(mount MountFunc, unmount UnmountFunc)
24 -
25 - // Unmount calls Close.
26 + // Unmounts the mount
27 Unmount() error
28
28 - ctxgroup.ContextGroup
29 + // CtxGroup returns the mount's CtxGroup to be able to link it
30 + // to other processes. Unmount upon closing.
31 + CtxGroup() ctxgroup.ContextGroup
32 }
33
31 -// UnmountFunc is a function used to Unmount a mount
32 -type UnmountFunc func(Mount) error
34 +// mount implements go-ipfs/fuse/mount
35 +type mount struct {
36 + mpoint string
37 + filesys fs.FS
38 + fuseConn *fuse.Conn
39 + // closeErr error
40 +
41 + cg ctxgroup.ContextGroup
42 +}
43 +
44 +// Mount mounts a fuse fs.FS at a given location, and returns a Mount instance.
45 +// parent is a ContextGroup to bind the mount's ContextGroup to.
46 +func NewMount(p ctxgroup.ContextGroup, fsys fs.FS, mountpoint string) (Mount, error) {
47 + conn, err := fuse.Mount(mountpoint)
48 + if err != nil {
49 + return nil, err
50 + }
51 +
52 + m := &mount{
53 + mpoint: mountpoint,
54 + fuseConn: conn,
55 + filesys: fsys,
56 + cg: ctxgroup.WithParent(p), // link it to parent.
57 + }
58 + m.cg.SetTeardown(m.unmount)
59
34 -// MountFunc is a function used to Mount a mount
35 -type MountFunc func(Mount) error
60 + // launch the mounting process.
61 + if err := m.mount(); err != nil {
62 + m.Unmount() // just in case.
63 + return nil, err
64 + }
65
37 -// New constructs a new Mount instance. ctx is a context to wait upon,
38 -// the mountpoint is the directory that the mount was mounted at, and unmount
39 -// in an UnmountFunc to perform the unmounting logic.
40 -func New(ctx context.Context, mountpoint string) Mount {
41 - m := &mount{mpoint: mountpoint}
42 - m.ContextGroup = ctxgroup.WithContextAndTeardown(ctx, m.persistentUnmount)
43 - return m
66 + return m, nil
67 }
68
46 -type mount struct {
47 - ctxgroup.ContextGroup
69 +func (m *mount) mount() error {
70 + log.Infof("Mounting %s", m.MountPoint())
71 +
72 + errs := make(chan error, 1)
73 + go func() {
74 + err := fs.Serve(m.fuseConn, m.filesys)
75 + log.Debugf("Mounting %s -- fs.Serve returned (%s)", err)
76 + errs <- err
77 + close(errs)
78 + }()
79 +
80 + // wait for the mount process to be done, or timed out.
81 + select {
82 + case <-time.After(MountTimeout):
83 + return fmt.Errorf("Mounting %s timed out.", m.MountPoint())
84 + case err := <-errs:
85 + return err
86 + case <-m.fuseConn.Ready:
87 + }
88
49 - unmount UnmountFunc
50 - mpoint string
89 + // check if the mount process has an error to report
90 + if err := m.fuseConn.MountError; err != nil {
91 + return err
92 + }
93 +
94 + log.Infof("Mounted %s", m.MountPoint())
95 + return nil
96 }
97
53 -// umount is called after the mount is closed.
54 -// TODO this is hacky, make it better.
55 -func (m *mount) persistentUnmount() error {
56 - // no unmount func.
57 - if m.unmount == nil {
98 +// umount is called exactly once to unmount this service.
99 +// note that closing the connection will not always unmount
100 +// properly. If that happens, we bring out the big guns
101 +// (mount.ForceUnmountManyTimes, exec unmount).
102 +func (m *mount) unmount() error {
103 + log.Infof("Unmounting %s", m.MountPoint())
104 +
105 + // try unmounting with fuse lib
106 + err := fuse.Unmount(m.MountPoint())
107 + if err == nil {
108 return nil
109 }
110 + log.Error("fuse unmount err: %s", err)
111
61 - // ok try to unmount a whole bunch of times...
62 - for i := 0; i < 34; i++ {
63 - err := m.unmount(m)
64 - if err == nil {
65 - return nil
66 - }
67 - time.Sleep(time.Millisecond * 300)
112 + // try closing the fuseConn
113 + err = m.fuseConn.Close()
114 + if err == nil {
115 + return nil
116 + }
117 + if err != nil {
118 + log.Error("fuse conn error: %s", err)
119 }
120
70 - // didnt work.
71 - return fmt.Errorf("Unmount %s failed after 10 seconds of trying.")
121 + // try mount.ForceUnmountManyTimes
122 + if err := ForceUnmountManyTimes(m, 10); err != nil {
123 + return err
124 + }
125 +
126 + log.Infof("Seemingly unmounted %s", m.MountPoint())
127 + return nil
128 +}
129 +
130 +func (m *mount) CtxGroup() ctxgroup.ContextGroup {
131 + return m.cg
132 }
133
134 func (m *mount) MountPoint() string {
@@ -76,17 +136,59 @@ func (m *mount) MountPoint() string {
136 }
137
138 func (m *mount) Unmount() error {
79 - return m.Close()
139 + // call ContextCloser Close(), which calls unmount() exactly once.
140 + return m.cg.Close()
141 }
142
82 -func (m *mount) Mount(mount MountFunc, unmount UnmountFunc) {
83 - m.unmount = unmount
143 +// ForceUnmount attempts to forcibly unmount a given mount.
144 +// It does so by calling diskutil or fusermount directly.
145 +func ForceUnmount(m Mount) error {
146 + point := m.MountPoint()
147 + log.Infof("Force-Unmounting %s...", point)
148 +
149 + var cmd *exec.Cmd
150 + switch runtime.GOOS {
151 + case "darwin":
152 + cmd = exec.Command("diskutil", "umount", "force", point)
153 + case "linux":
154 + cmd = exec.Command("fusermount", "-u", point)
155 + default:
156 + return fmt.Errorf("unmount: unimplemented")
157 + }
158 +
159 + errc := make(chan error, 1)
160 + go func() {
161 + defer close(errc)
162
85 - // go serve the mount
86 - m.ContextGroup.AddChildFunc(func(parent ctxgroup.ContextGroup) {
87 - if err := mount(m); err != nil {
88 - log.Error("%s mount: %s", m.MountPoint(), err)
163 + // try vanilla unmount first.
164 + if err := exec.Command("umount", point).Run(); err == nil {
165 + return
166 }
90 - m.Unmount()
91 - })
167 +
168 + // retry to unmount with the fallback cmd
169 + errc <- cmd.Run()
170 + }()
171 +
172 + select {
173 + case <-time.After(2 * time.Second):
174 + return fmt.Errorf("umount timeout")
175 + case err := <-errc:
176 + return err
177 + }
178 +}
179 +
180 +// ForceUnmountManyTimes attempts to forcibly unmount a given mount,
181 +// many times. It does so by calling diskutil or fusermount directly.
182 +// Attempts a given number of times.
183 +func ForceUnmountManyTimes(m Mount, attempts int) error {
184 + var err error
185 + for i := 0; i < attempts; i++ {
186 + err = ForceUnmount(m)
187 + if err == nil {
188 + return err
189 + }
190 +
191 + <-time.After(time.Millisecond * 500)
192 + }
193 + return fmt.Errorf("Unmount %s failed after 10 seconds of trying.", m.MountPoint())
194 }
fuse/readonly/mount_unix.go new
+14
@@ -0,0 +1,14 @@
1 +// +build linux darwin freebsd
2 +
3 +package readonly
4 +
5 +import (
6 + core "github.com/jbenet/go-ipfs/core"
7 + mount "github.com/jbenet/go-ipfs/fuse/mount"
8 +)
9 +
10 +// Mount mounts ipfs at a given location, and returns a mount.Mount instance.
11 +func Mount(ipfs *core.IpfsNode, mountpoint string) (mount.Mount, error) {
12 + fsys := NewFileSystem(ipfs)
13 + return mount.NewMount(ipfs, fsys, mountpoint)
14 +}
fuse/readonly/readonly_unix.go
-88
@@ -5,19 +5,14 @@
5 package readonly
6
7 import (
8 - "fmt"
8 "io/ioutil"
9 "os"
11 - "os/exec"
12 - "runtime"
13 - "time"
10
11 fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
12 fs "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
13 proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
14
15 core "github.com/jbenet/go-ipfs/core"
20 - mount "github.com/jbenet/go-ipfs/fuse/mount"
16 mdag "github.com/jbenet/go-ipfs/merkledag"
17 uio "github.com/jbenet/go-ipfs/unixfs/io"
18 ftpb "github.com/jbenet/go-ipfs/unixfs/pb"
@@ -158,86 +153,3 @@ func (s *Node) ReadAll(intr fs.Intr) ([]byte, fuse.Error) {
153 // what if i have a 6TB file? GG RAM.
154 return ioutil.ReadAll(r)
155 }
161 -
162 -// Mount mounts an IpfsNode instance at a particular path. It
163 -// serves until the process receives exit signals (to Unmount).
164 -func Mount(ipfs *core.IpfsNode, fpath string) (mount.Mount, error) {
165 - log.Infof("Mounting ipfs at %s...", fpath)
166 -
167 - // setup the Mount abstraction.
168 - m := mount.New(ipfs.Context(), fpath)
169 -
170 - // go serve the mount
171 - m.Mount(func(m mount.Mount) error {
172 - return internalMount(ipfs, m)
173 - }, internalUnmount)
174 -
175 - select {
176 - case <-m.Closed():
177 - return nil, fmt.Errorf("failed to mount")
178 - case <-time.After(time.Second):
179 - // assume it worked...
180 - }
181 -
182 - // bind the mount (ContextGroup) to the node, so that when the node exits
183 - // the fsclosers are automatically closed.
184 - ipfs.AddChildGroup(m)
185 - return m, nil
186 -}
187 -
188 -// mount attempts to mount the provided FUSE mount point
189 -func internalMount(ipfs *core.IpfsNode, m mount.Mount) error {
190 - c, err := fuse.Mount(m.MountPoint())
191 - if err != nil {
192 - return err
193 - }
194 - defer c.Close()
195 -
196 - fsys := FileSystem{Ipfs: ipfs}
197 -
198 - log.Infof("Mounted ipfs at %s.", m.MountPoint())
199 - if err := fs.Serve(c, fsys); err != nil {
200 - return err
201 - }
202 -
203 - // check if the mount process has an error to report
204 - <-c.Ready
205 - if err := c.MountError; err != nil {
206 - m.Unmount()
207 - return err
208 - }
209 - return nil
210 -}
211 -
212 -// unmount attempts to unmount the provided FUSE mount point, forcibly
213 -// if necessary.
214 -func internalUnmount(m mount.Mount) error {
215 - point := m.MountPoint()
216 - log.Infof("Unmounting ipfs at %s...", point)
217 -
218 - var cmd *exec.Cmd
219 - switch runtime.GOOS {
220 - case "darwin":
221 - cmd = exec.Command("diskutil", "umount", "force", point)
222 - case "linux":
223 - cmd = exec.Command("fusermount", "-u", point)
224 - default:
225 - return fmt.Errorf("unmount: unimplemented")
226 - }
227 -
228 - errc := make(chan error, 1)
229 - go func() {
230 - if err := exec.Command("umount", point).Run(); err == nil {
231 - errc <- err
232 - }
233 - // retry to unmount with the fallback cmd
234 - errc <- cmd.Run()
235 - }()
236 -
237 - select {
238 - case <-time.After(1 * time.Second):
239 - return fmt.Errorf("umount timeout")
240 - case err := <-errc:
241 - return err
242 - }
243 -}