mount: fixed mount init + teardown
This commit adds a Mount abstraction (which is really just a wrapped context closer). It makes sure to bind the mount to the fate of the Node (i.e. close it if the node ends). This fixes #350
Juan Batiz-Benet committed
Nov 16, 2014 at 01:42 UTC
99f2378bac6df4cdb09e6485d374eb9d0606c22f
6 files changed
+227
-116
cmd/ipfs2/main.go
+1
@@ -297,6 +297,7 @@ func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
297
// this is gross, and should be changed when we extract out the exec Context.
298
node := req.Context().NodeWithoutConstructing()
299
if node != nil {
300
+ log.Info("Shutting down node...")
301
node.Close()
302
}
303
}
core/commands2/mount_unix.go
+46
-43
@@ -11,6 +11,7 @@ import (
11
config "github.com/jbenet/go-ipfs/config"
12
core "github.com/jbenet/go-ipfs/core"
13
ipns "github.com/jbenet/go-ipfs/fuse/ipns"
14
+ mount "github.com/jbenet/go-ipfs/fuse/mount"
15
rofs "github.com/jbenet/go-ipfs/fuse/readonly"
16
)
17
@@ -113,7 +114,6 @@ baz
114
if !found {
115
fsdir = cfg.Mounts.IPFS // use default value
116
}
116
- fsdone := mountIpfs(node, fsdir)
117
118
// get default mount points
119
nsdir, found, err := req.Option("n").String()
@@ -124,30 +124,14 @@ baz
124
nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare!
125
}
126
127
- nsdone := mountIpns(node, nsdir, fsdir)
128
-
129
- fmtFuseErr := func(err error) error {
130
- s := err.Error()
131
- if strings.Contains(s, fuseNoDirectory) {
132
- s = strings.Replace(s, `fusermount: "fusermount:`, "", -1)
133
- s = strings.Replace(s, `\n", exit status 1`, "", -1)
134
- return cmds.ClientError(s)
135
- }
136
- return err
127
+ if err := doMount(node, fsdir, nsdir); err != nil {
128
+ return nil, err
129
}
130
139
- // wait until mounts return an error (or timeout if successful)
140
- select {
141
- case err := <-fsdone:
142
- return nil, fmtFuseErr(err)
143
- case err := <-nsdone:
144
- return nil, fmtFuseErr(err)
145
-
146
- // mounted successfully, we timed out with no errors
147
- case <-time.After(mountTimeout):
148
- output := cfg.Mounts
149
- return &output, nil
150
- }
131
+ var output config.Mounts
132
+ output.IPFS = fsdir
133
+ output.IPNS = nsdir
134
+ return &output, nil
135
},
136
Type: &config.Mounts{},
137
Marshalers: cmds.MarshalerMap{
@@ -160,33 +144,52 @@ baz
144
},
145
}
146
163
-func mountIpfs(node *core.IpfsNode, fsdir string) <-chan error {
164
- done := make(chan error)
165
- log.Info("Mounting IPFS at ", fsdir)
147
+func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
148
+ fmtFuseErr := func(err error) error {
149
+ s := err.Error()
150
+ if strings.Contains(s, fuseNoDirectory) {
151
+ s = strings.Replace(s, `fusermount: "fusermount:`, "", -1)
152
+ s = strings.Replace(s, `\n", exit status 1`, "", -1)
153
+ return cmds.ClientError(s)
154
+ }
155
+ return err
156
+ }
157
167
- go func() {
168
- err := rofs.Mount(node, fsdir)
169
- done <- err
170
- close(done)
171
- }()
158
+ // this sync stuff is so that both can be mounted simultaneously.
159
+ var fsmount mount.Mount
160
+ var nsmount mount.Mount
161
+ var err1 error
162
+ var err2 error
163
173
- return done
174
-}
164
+ done := make(chan struct{})
165
176
-func mountIpns(node *core.IpfsNode, nsdir, fsdir string) <-chan error {
177
- if nsdir == "" {
178
- return nil
179
- }
180
- done := make(chan error)
181
- log.Info("Mounting IPNS at ", nsdir)
166
+ go func() {
167
+ fsmount, err1 = rofs.Mount(node, fsdir)
168
+ done <- struct{}{}
169
+ }()
170
171
go func() {
184
- err := ipns.Mount(node, nsdir, fsdir)
185
- done <- err
186
- close(done)
172
+ nsmount, err2 = ipns.Mount(node, nsdir, fsdir)
173
+ done <- struct{}{}
174
}()
175
189
- return done
176
+ <-done
177
+ <-done
178
+
179
+ if err1 != nil || err2 != nil {
180
+ fsmount.Close()
181
+ nsmount.Close()
182
+ if err1 != nil {
183
+ return fmtFuseErr(err1)
184
+ } else {
185
+ return fmtFuseErr(err2)
186
+ }
187
+ }
188
+
189
+ // setup node state, so that it can be cancelled
190
+ node.Mounts.Ipfs = fsmount
191
+ node.Mounts.Ipns = nsmount
192
+ return nil
193
}
194
195
var platformFuseChecks = func() error {
core/core.go
+12
@@ -27,6 +27,7 @@ import (
27
routing "github.com/jbenet/go-ipfs/routing"
28
dht "github.com/jbenet/go-ipfs/routing/dht"
29
u "github.com/jbenet/go-ipfs/util"
30
+ mount "github.com/jbenet/go-ipfs/fuse/mount"
31
ctxc "github.com/jbenet/go-ipfs/util/ctxcloser"
32
)
33
@@ -74,11 +75,22 @@ type IpfsNode struct {
75
// the pinning manager
76
Pinning pin.Pinner
77
78
+ // current mount state, if any.
79
+ Mounts Mounts
80
+
81
ctxc.ContextCloser
82
83
onlineMode bool // alternatively, offline
84
}
85
86
+// Mounts defines what the node's mount state is. This should
87
+// perhaps be moved to the daemon or mount. It's here because
88
+// it needs to be accessible across daemon requests.
89
+type Mounts struct {
90
+ Ipfs mount.Mount
91
+ Ipns mount.Mount
92
+}
93
+
94
// NewIpfsNode constructs a new IpfsNode based on the given config.
95
func NewIpfsNode(cfg *config.Config, online bool) (n *IpfsNode, err error) {
96
success := false // flip to true after all sub-system inits succeed
fuse/ipns/mount_unix.go
+44
-41
@@ -2,66 +2,69 @@ package ipns
2
3
import (
4
"fmt"
5
- "os"
5
"os/exec"
7
- "os/signal"
6
"runtime"
9
- "syscall"
7
"time"
8
12
- "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
13
- "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
14
- "github.com/jbenet/go-ipfs/core"
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
+
12
+ core "github.com/jbenet/go-ipfs/core"
13
+ mount "github.com/jbenet/go-ipfs/fuse/mount"
14
)
15
16
// Mount mounts an IpfsNode instance at a particular path. It
17
// serves until the process receives exit signals (to Unmount).
19
-func Mount(ipfs *core.IpfsNode, fpath string, ipfspath string) error {
18
+func Mount(ipfs *core.IpfsNode, fpath string, ipfspath string) (mount.Mount, error) {
19
+ log.Infof("Mounting ipns at %s...", fpath)
20
21
- sigc := make(chan os.Signal, 1)
22
- signal.Notify(sigc, syscall.SIGHUP, syscall.SIGINT,
23
- syscall.SIGTERM, syscall.SIGQUIT)
21
+ // setup the Mount abstraction.
22
+ m := mount.New(ipfs.Context(), fpath, unmount)
23
25
- go func() {
26
- defer ipfs.Network.Close()
27
- <-sigc
28
- for {
29
- err := Unmount(fpath)
30
- if err == nil {
31
- return
32
- }
33
- time.Sleep(time.Millisecond * 100)
24
+ // go serve the mount
25
+ mount.ServeMount(m, func(m mount.Mount) error {
26
+
27
+ c, err := fuse.Mount(fpath)
28
+ if err != nil {
29
+ return err
30
}
35
- }()
31
+ defer c.Close()
32
37
- c, err := fuse.Mount(fpath)
38
- if err != nil {
39
- return err
40
- }
41
- defer c.Close()
33
+ fsys, err := NewIpns(ipfs, ipfspath)
34
+ if err != nil {
35
+ return err
36
+ }
37
43
- fsys, err := NewIpns(ipfs, ipfspath)
44
- if err != nil {
45
- return err
46
- }
38
+ log.Infof("Mounted ipns at %s.", fpath)
39
+ if err := fs.Serve(c, fsys); err != nil {
40
+ return err
41
+ }
42
48
- err = fs.Serve(c, fsys)
49
- if err != nil {
50
- return err
51
- }
43
+ // check if the mount process has an error to report
44
+ <-c.Ready
45
+ if err := c.MountError; err != nil {
46
+ return err
47
+ }
48
+ return nil
49
+ })
50
53
- // check if the mount process has an error to report
54
- <-c.Ready
55
- if err := c.MountError; err != nil {
56
- return err
51
+ select {
52
+ case <-m.Closed():
53
+ return nil, fmt.Errorf("failed to mount")
54
+ case <-time.After(time.Second):
55
+ // assume it worked...
56
}
58
- return nil
57
+
58
+ // bind the mount (ContextCloser) to the node, so that when the node exits
59
+ // the fsclosers are automatically closed.
60
+ ipfs.AddCloserChild(m)
61
+ return m, nil
62
}
63
61
-// Unmount attempts to unmount the provided FUSE mount point, forcibly
64
+// unmount attempts to unmount the provided FUSE mount point, forcibly
65
// if necessary.
63
-func Unmount(point string) error {
64
- fmt.Printf("Unmounting %s...\n", point)
66
+func unmount(point string) error {
67
+ log.Infof("Unmounting ipns at %s...", point)
68
69
var cmd *exec.Cmd
70
switch runtime.GOOS {
fuse/mount/mount.go
new
+86
@@ -0,0 +1,86 @@
1
+// package mount provides a simple abstraction around a mount point
2
+package mount
3
+
4
+import (
5
+ "fmt"
6
+ "time"
7
+
8
+ context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
9
+
10
+ u "github.com/jbenet/go-ipfs/util"
11
+ ctxc "github.com/jbenet/go-ipfs/util/ctxcloser"
12
+)
13
+
14
+var log = u.Logger("mount")
15
+
16
+// Mount represents a filesystem mount
17
+type Mount interface {
18
+
19
+ // MountPoint is the path at which this mount is mounted
20
+ MountPoint() string
21
+
22
+ // Unmount calls Close.
23
+ Unmount() error
24
+
25
+ ctxc.ContextCloser
26
+}
27
+
28
+// UnmountFunc is a function used to unmount a mount
29
+type UnmountFunc func(mountpoint string) error
30
+
31
+// New constructs a new Mount instance. ctx is a context to wait upon,
32
+// the mountpoint is the directory that the mount was mounted at, and unmount
33
+// in an UnmountFunc to perform the unmounting logic.
34
+func New(ctx context.Context, mountpoint string, unmount UnmountFunc) Mount {
35
+ m := &mount{
36
+ mpoint: mountpoint,
37
+ unmount: unmount,
38
+ }
39
+ m.ContextCloser = ctxc.NewContextCloser(ctx, m.persistentUnmount)
40
+ return m
41
+}
42
+
43
+type mount struct {
44
+ ctxc.ContextCloser
45
+
46
+ unmount UnmountFunc
47
+ mpoint string
48
+}
49
+
50
+// umount is called after the mount is closed.
51
+// TODO this is hacky, make it better.
52
+func (m *mount) persistentUnmount() error {
53
+
54
+ // ok try to unmount a whole bunch of times...
55
+ for i := 0; i < 34; i++ {
56
+ err := m.unmount(m.mpoint)
57
+ if err == nil {
58
+ return nil
59
+ }
60
+ time.Sleep(time.Millisecond * 300)
61
+ }
62
+
63
+ // didnt work.
64
+ return fmt.Errorf("Unmount %s failed after 10 seconds of trying.")
65
+}
66
+
67
+func (m *mount) MountPoint() string {
68
+ return m.mpoint
69
+}
70
+
71
+func (m *mount) Unmount() error {
72
+ return m.Close()
73
+}
74
+
75
+func ServeMount(m Mount, mount func(Mount) error) {
76
+ m.Children().Add(1)
77
+
78
+ // go serve the mount
79
+ go func() {
80
+ if err := mount(m); err != nil {
81
+ log.Error("%s mount: %s", m.MountPoint(), err)
82
+ }
83
+ m.Children().Done()
84
+ m.Unmount()
85
+ }()
86
+}
fuse/readonly/readonly_unix.go
+38
-32
@@ -9,9 +9,7 @@ import (
9
"io/ioutil"
10
"os"
11
"os/exec"
12
- "os/signal"
12
"runtime"
14
- "syscall"
13
"time"
14
15
fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
@@ -19,6 +17,7 @@ import (
17
proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
18
19
core "github.com/jbenet/go-ipfs/core"
20
+ mount "github.com/jbenet/go-ipfs/fuse/mount"
21
mdag "github.com/jbenet/go-ipfs/merkledag"
22
uio "github.com/jbenet/go-ipfs/unixfs/io"
23
ftpb "github.com/jbenet/go-ipfs/unixfs/pb"
@@ -162,47 +161,54 @@ func (s *Node) ReadAll(intr fs.Intr) ([]byte, fuse.Error) {
161
162
// Mount mounts an IpfsNode instance at a particular path. It
163
// serves until the process receives exit signals (to Unmount).
165
-func Mount(ipfs *core.IpfsNode, fpath string) error {
164
+func Mount(ipfs *core.IpfsNode, fpath string) (mount.Mount, error) {
165
+ log.Infof("Mounting ipfs at %s...", fpath)
166
167
- sigc := make(chan os.Signal, 1)
168
- signal.Notify(sigc, syscall.SIGHUP, syscall.SIGINT,
169
- syscall.SIGTERM, syscall.SIGQUIT)
167
+ // setup the Mount abstraction.
168
+ m := mount.New(ipfs.Context(), fpath, unmount)
169
171
- go func() {
172
- defer ipfs.Network.Close()
173
- <-sigc
174
- for {
175
- err := Unmount(fpath)
176
- if err == nil {
177
- return
178
- }
179
- time.Sleep(time.Millisecond * 10)
170
+ // go serve the mount
171
+ mount.ServeMount(m, func(m mount.Mount) error {
172
+
173
+ c, err := fuse.Mount(m.MountPoint())
174
+ if err != nil {
175
+ return err
176
}
181
- }()
177
+ defer c.Close()
178
183
- c, err := fuse.Mount(fpath)
184
- if err != nil {
185
- return err
186
- }
187
- defer c.Close()
179
+ fsys := FileSystem{Ipfs: ipfs}
180
189
- err = fs.Serve(c, FileSystem{Ipfs: ipfs})
190
- if err != nil {
191
- return err
192
- }
181
+ log.Infof("Mounted ipfs at %s.", fpath)
182
+ if err := fs.Serve(c, fsys); err != nil {
183
+ return err
184
+ }
185
194
- // check if the mount process has an error to report
195
- <-c.Ready
196
- if err := c.MountError; err != nil {
197
- return err
186
+ // check if the mount process has an error to report
187
+ <-c.Ready
188
+ if err := c.MountError; err != nil {
189
+ m.Unmount()
190
+ return err
191
+ }
192
+ return nil
193
+ })
194
+
195
+ select {
196
+ case <-m.Closed():
197
+ return nil, fmt.Errorf("failed to mount")
198
+ case <-time.After(time.Second):
199
+ // assume it worked...
200
}
199
- return nil
201
+
202
+ // bind the mount (ContextCloser) to the node, so that when the node exits
203
+ // the fsclosers are automatically closed.
204
+ ipfs.AddCloserChild(m)
205
+ return m, nil
206
}
207
208
// Unmount attempts to unmount the provided FUSE mount point, forcibly
209
// if necessary.
204
-func Unmount(point string) error {
205
- log.Info("Unmounting %s...", point)
210
+func unmount(point string) error {
211
+ log.Infof("Unmounting ipfs at %s...", point)
212
213
var cmd *exec.Cmd
214
switch runtime.GOOS {