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