@cryptotaxi247 / kubo / commits / 99f2378ba

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 {