@cryptotaxi247 / kubo / commits / 40a49c839

Mounts detect unmounts and track mount state.

This lets FUSE mounts to track whether they are active or not by tracking when fs.Serve terminates. License: MIT Signed-off-by: Stephen Whitmore <noffle@ipfs.io>

Stephen Whitmore committed Feb 4, 2016 at 12:55 UTC 40a49c8399b8e952c6852909f55ba5eea8d4d591
13 files changed +296 -243
cmd/ipfs/daemon.go
+2 -1
@@ -21,6 +21,7 @@ import (
21 corehttp "github.com/ipfs/go-ipfs/core/corehttp"
22 corerepo "github.com/ipfs/go-ipfs/core/corerepo"
23 "github.com/ipfs/go-ipfs/core/corerouting"
24 + nodeMount "github.com/ipfs/go-ipfs/fuse/node"
25 fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
26 util "github.com/ipfs/go-ipfs/util"
27 conn "gx/ipfs/QmUBogf4nUefBjmYjn6jfsfPJRkmDGSeMhNj4usRKq69f4/go-libp2p/p2p/net/conn"
@@ -494,7 +495,7 @@ func mountFuse(req cmds.Request) error {
495 return fmt.Errorf("mountFuse: ConstructNode() failed: %s", err)
496 }
497
497 - err = commands.Mount(node, fsdir, nsdir)
498 + err = nodeMount.Mount(node, fsdir, nsdir)
499 if err != nil {
500 return err
501 }
core/commands/mount_nofuse.go
-7
@@ -4,10 +4,7 @@
4 package commands
5
6 import (
7 - "errors"
8 -
7 cmds "github.com/ipfs/go-ipfs/commands"
10 - "github.com/ipfs/go-ipfs/core"
8 )
9
10 var MountCmd = &cmds.Command{
@@ -23,7 +20,3 @@ For the latest instructions, please check the project's repository:
20 `,
21 },
22 }
26 -
27 -func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
28 - return errors.New("not compiled in")
29 -}
core/commands/mount_unix.go
+2 -109
@@ -7,32 +7,12 @@ import (
7 "fmt"
8 "io"
9 "strings"
10 - "time"
10
11 cmds "github.com/ipfs/go-ipfs/commands"
13 - core "github.com/ipfs/go-ipfs/core"
14 - ipns "github.com/ipfs/go-ipfs/fuse/ipns"
15 - mount "github.com/ipfs/go-ipfs/fuse/mount"
16 - rofs "github.com/ipfs/go-ipfs/fuse/readonly"
12 + nodeMount "github.com/ipfs/go-ipfs/fuse/node"
13 config "github.com/ipfs/go-ipfs/repo/config"
14 )
15
20 -// amount of time to wait for mount errors
21 -// TODO is this non-deterministic?
22 -const mountTimeout = time.Second
23 -
24 -// fuseNoDirectory used to check the returning fuse error
25 -const fuseNoDirectory = "fusermount: failed to access mountpoint"
26 -
27 -// fuseExitStatus1 used to check the returning fuse error
28 -const fuseExitStatus1 = "fusermount: exit status 1"
29 -
30 -// platformFuseChecks can get overridden by arch-specific files
31 -// to run fuse checks (like checking the OSXFUSE version)
32 -var platformFuseChecks = func(*core.IpfsNode) error {
33 - return nil
34 -}
35 -
16 var MountCmd = &cmds.Command{
17 Helptext: cmds.HelpText{
18 Tagline: "Mounts IPFS to the filesystem (read-only).",
@@ -134,7 +114,7 @@ baz
114 nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare!
115 }
116
137 - err = Mount(node, fsdir, nsdir)
117 + err = nodeMount.Mount(node, fsdir, nsdir)
118 if err != nil {
119 res.SetError(err, cmds.ErrNormal)
120 return
@@ -155,90 +135,3 @@ baz
135 },
136 },
137 }
158 -
159 -func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
160 - // check if we already have live mounts.
161 - // if the user said "Mount", then there must be something wrong.
162 - // so, close them and try again.
163 - if node.Mounts.Ipfs != nil {
164 - node.Mounts.Ipfs.Unmount()
165 - }
166 - if node.Mounts.Ipns != nil {
167 - node.Mounts.Ipns.Unmount()
168 - }
169 -
170 - if err := platformFuseChecks(node); err != nil {
171 - return err
172 - }
173 -
174 - var err error
175 - if err = doMount(node, fsdir, nsdir); err != nil {
176 - return err
177 - }
178 -
179 - return nil
180 -}
181 -
182 -func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
183 - fmtFuseErr := func(err error, mountpoint string) error {
184 - s := err.Error()
185 - if strings.Contains(s, fuseNoDirectory) {
186 - s = strings.Replace(s, `fusermount: "fusermount:`, "", -1)
187 - s = strings.Replace(s, `\n", exit status 1`, "", -1)
188 - return cmds.ClientError(s)
189 - }
190 - if s == fuseExitStatus1 {
191 - s = fmt.Sprintf("fuse failed to access mountpoint %s", mountpoint)
192 - return cmds.ClientError(s)
193 - }
194 - return err
195 - }
196 -
197 - // this sync stuff is so that both can be mounted simultaneously.
198 - var fsmount mount.Mount
199 - var nsmount mount.Mount
200 - var err1 error
201 - var err2 error
202 -
203 - done := make(chan struct{})
204 -
205 - go func() {
206 - fsmount, err1 = rofs.Mount(node, fsdir)
207 - done <- struct{}{}
208 - }()
209 -
210 - go func() {
211 - nsmount, err2 = ipns.Mount(node, nsdir, fsdir)
212 - done <- struct{}{}
213 - }()
214 -
215 - <-done
216 - <-done
217 -
218 - if err1 != nil {
219 - log.Errorf("error mounting: %s", err1)
220 - }
221 -
222 - if err2 != nil {
223 - log.Errorf("error mounting: %s", err2)
224 - }
225 -
226 - if err1 != nil || err2 != nil {
227 - if fsmount != nil {
228 - fsmount.Unmount()
229 - }
230 - if nsmount != nil {
231 - nsmount.Unmount()
232 - }
233 -
234 - if err1 != nil {
235 - return fmtFuseErr(err1, fsdir)
236 - }
237 - return fmtFuseErr(err2, nsdir)
238 - }
239 -
240 - // setup node state, so that it can be cancelled
241 - node.Mounts.Ipfs = fsmount
242 - node.Mounts.Ipns = nsmount
243 - return nil
244 -}
core/commands/mount_windows.go
-7
@@ -4,7 +4,6 @@ import (
4 "errors"
5
6 cmds "github.com/ipfs/go-ipfs/commands"
7 - "github.com/ipfs/go-ipfs/core"
7 )
8
9 var MountCmd = &cmds.Command{
@@ -17,9 +16,3 @@ var MountCmd = &cmds.Command{
16 res.SetError(errors.New("Mount isn't compatible with Windows yet"), cmds.ErrNormal)
17 },
18 }
20 -
21 -func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
22 - // TODO
23 - // currently a no-op, but we don't want to return an error
24 - return nil
25 -}
core/core.go
+2 -2
@@ -335,10 +335,10 @@ func (n *IpfsNode) teardown() error {
335 closers = append(closers, n.Exchange)
336 }
337
338 - if n.Mounts.Ipfs != nil {
338 + if n.Mounts.Ipfs != nil && !n.Mounts.Ipfs.IsActive() {
339 closers = append(closers, mount.Closer(n.Mounts.Ipfs))
340 }
341 - if n.Mounts.Ipns != nil {
341 + if n.Mounts.Ipns != nil && !n.Mounts.Ipns.IsActive() {
342 closers = append(closers, mount.Closer(n.Mounts.Ipns))
343 }
344
fuse/ipns/ipns_test.go
+1 -107
@@ -15,12 +15,11 @@ import (
15 racedet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-detect-race"
16
17 core "github.com/ipfs/go-ipfs/core"
18 - context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
19 - //mfs "github.com/ipfs/go-ipfs/mfs"
18 namesys "github.com/ipfs/go-ipfs/namesys"
19 offroute "github.com/ipfs/go-ipfs/routing/offline"
20 u "github.com/ipfs/go-ipfs/util"
21 ci "github.com/ipfs/go-ipfs/util/testutil/ci"
22 + context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
23 )
24
25 func maybeSkipFuseTests(t *testing.T) {
@@ -437,111 +436,6 @@ func TestFSThrash(t *testing.T) {
436 }
437 }
438
440 -/*
441 -func TestFastRepublish(t *testing.T) {
442 - if testing.Short() {
443 - t.SkipNow()
444 - }
445 -
446 - // make timeout noticeable.
447 - osrt := shortRepublishTimeout
448 - shortRepublishTimeout = time.Millisecond * 100
449 -
450 - olrt := longRepublishTimeout
451 - longRepublishTimeout = time.Second
452 -
453 - node, mnt := setupIpnsTest(t, nil)
454 -
455 - h, err := node.PrivateKey.GetPublic().Hash()
456 - if err != nil {
457 - t.Fatal(err)
458 - }
459 - pubkeyPath := "/ipns/" + u.Key(h).String()
460 -
461 - // set them back
462 - defer func() {
463 - shortRepublishTimeout = osrt
464 - longRepublishTimeout = olrt
465 - mnt.Close()
466 - }()
467 -
468 - closed := make(chan struct{})
469 - dataA := []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
470 - dataB := []byte("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
471 -
472 - fname := mnt.Dir + "/local/file"
473 -
474 - // get first resolved hash
475 - log.Debug("publishing first hash")
476 - writeFileData(t, dataA, fname) // random
477 - <-time.After(shortRepublishTimeout * 2)
478 - log.Debug("resolving first hash")
479 - resolvedHash, err := node.Namesys.Resolve(context.Background(), pubkeyPath)
480 - if err != nil {
481 - t.Fatal("resolve err:", pubkeyPath, err)
482 - }
483 -
484 - // constantly keep writing to the file
485 - go func(timeout time.Duration) {
486 - for {
487 - select {
488 - case <-closed:
489 - return
490 -
491 - case <-time.After(timeout * 8 / 10):
492 - writeFileData(t, dataB, fname)
493 - }
494 - }
495 - }(shortRepublishTimeout)
496 -
497 - hasPublished := func() bool {
498 - res, err := node.Namesys.Resolve(context.Background(), pubkeyPath)
499 - if err != nil {
500 - t.Fatalf("resolve err: %v", err)
501 - }
502 - return res != resolvedHash
503 - }
504 -
505 - // test things
506 -
507 - // at this point, should not have written dataA and not have written dataB
508 - rbuf, err := ioutil.ReadFile(fname)
509 - if err != nil || !bytes.Equal(rbuf, dataA) {
510 - t.Fatalf("Data inconsistent! %v %v", err, string(rbuf))
511 - }
512 -
513 - if hasPublished() {
514 - t.Fatal("published (wrote)")
515 - }
516 -
517 - <-time.After(shortRepublishTimeout * 11 / 10)
518 -
519 - // at this point, should have written written dataB, but not published it
520 - rbuf, err = ioutil.ReadFile(fname)
521 - if err != nil || !bytes.Equal(rbuf, dataB) {
522 - t.Fatalf("Data inconsistent! %v %v", err, string(rbuf))
523 - }
524 -
525 - if hasPublished() {
526 - t.Fatal("published (wrote)")
527 - }
528 -
529 - <-time.After(longRepublishTimeout * 11 / 10)
530 -
531 - // at this point, should have written written dataB, and published it
532 - rbuf, err = ioutil.ReadFile(fname)
533 - if err != nil || !bytes.Equal(rbuf, dataB) {
534 - t.Fatalf("Data inconsistent! %v %v", err, string(rbuf))
535 - }
536 -
537 - if !hasPublished() {
538 - t.Fatal("not published")
539 - }
540 -
541 - close(closed)
542 -}
543 -*/
544 -
439 // Test writing a medium sized file one byte at a time
440 func TestMultiWrite(t *testing.T) {
441
fuse/mount/fuse.go
+40 -9
@@ -4,7 +4,9 @@
4 package mount
5
6 import (
7 + "errors"
8 "fmt"
9 + "sync"
10 "time"
11
12 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
@@ -12,12 +14,16 @@ import (
14 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
15 )
16
17 +var ErrNotMounted = errors.New("not mounted")
18 +
19 // mount implements go-ipfs/fuse/mount
20 type mount struct {
21 mpoint string
22 filesys fs.FS
23 fuseConn *fuse.Conn
20 - // closeErr error
24 +
25 + active bool
26 + activeLock *sync.RWMutex
27
28 proc goprocess.Process
29 }
@@ -39,10 +45,12 @@ func NewMount(p goprocess.Process, fsys fs.FS, mountpoint string, allow_other bo
45 }
46
47 m := &mount{
42 - mpoint: mountpoint,
43 - fuseConn: conn,
44 - filesys: fsys,
45 - proc: goprocess.WithParent(p), // link it to parent.
48 + mpoint: mountpoint,
49 + fuseConn: conn,
50 + filesys: fsys,
51 + active: false,
52 + activeLock: &sync.RWMutex{},
53 + proc: goprocess.WithParent(p), // link it to parent.
54 }
55 m.proc.SetTeardown(m.unmount)
56
@@ -60,11 +68,14 @@ func (m *mount) mount() error {
68
69 errs := make(chan error, 1)
70 go func() {
71 + // fs.Serve blocks until the filesystem is unmounted.
72 err := fs.Serve(m.fuseConn, m.filesys)
64 - log.Debugf("Mounting %s -- fs.Serve returned (%s)", err)
73 + log.Debugf("%s is unmounted", m.MountPoint())
74 if err != nil {
75 + log.Debugf("fs.Serve returned (%s)", err)
76 errs <- err
77 }
78 + m.setActive(false)
79 }()
80
81 // wait for the mount process to be done, or timed out.
@@ -81,6 +92,8 @@ func (m *mount) mount() error {
92 return err
93 }
94
95 + m.setActive(true)
96 +
97 log.Infof("Mounted %s", m.MountPoint())
98 return nil
99 }
@@ -95,6 +108,7 @@ func (m *mount) unmount() error {
108 // try unmounting with fuse lib
109 err := fuse.Unmount(m.MountPoint())
110 if err == nil {
111 + m.setActive(false)
112 return nil
113 }
114 log.Warningf("fuse unmount err: %s", err)
@@ -102,11 +116,10 @@ func (m *mount) unmount() error {
116 // try closing the fuseConn
117 err = m.fuseConn.Close()
118 if err == nil {
119 + m.setActive(false)
120 return nil
121 }
107 - if err != nil {
108 - log.Warningf("fuse conn error: %s", err)
109 - }
122 + log.Warningf("fuse conn error: %s", err)
123
124 // try mount.ForceUnmountManyTimes
125 if err := ForceUnmountManyTimes(m, 10); err != nil {
@@ -114,6 +127,7 @@ func (m *mount) unmount() error {
127 }
128
129 log.Infof("Seemingly unmounted %s", m.MountPoint())
130 + m.setActive(false)
131 return nil
132 }
133
@@ -126,6 +140,23 @@ func (m *mount) MountPoint() string {
140 }
141
142 func (m *mount) Unmount() error {
143 + if !m.IsActive() {
144 + return ErrNotMounted
145 + }
146 +
147 // call Process Close(), which calls unmount() exactly once.
148 return m.proc.Close()
149 }
150 +
151 +func (m *mount) IsActive() bool {
152 + m.activeLock.RLock()
153 + defer m.activeLock.RUnlock()
154 +
155 + return m.active
156 +}
157 +
158 +func (m *mount) setActive(a bool) {
159 + m.activeLock.Lock()
160 + m.active = a
161 + m.activeLock.Unlock()
162 +}
fuse/mount/mount.go
+3
@@ -25,6 +25,9 @@ type Mount interface {
25 // Unmounts the mount
26 Unmount() error
27
28 + // Checks if the mount is still active.
29 + IsActive() bool
30 +
31 // Process returns the mount's Process to be able to link it
32 // to other processes. Unmount upon closing.
33 Process() goprocess.Process
fuse/node/mount_darwin.go renamed
+1 -1
@@ -1,6 +1,6 @@
1 // +build !nofuse
2
3 -package commands
3 +package node
4
5 import (
6 "bytes"
fuse/node/mount_nofuse.go new
+14
@@ -0,0 +1,14 @@
1 +// +build linux darwin freebsd
2 +// +build nofuse
3 +
4 +package node
5 +
6 +import (
7 + "errors"
8 +
9 + core "github.com/ipfs/go-ipfs/core"
10 +)
11 +
12 +func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
13 + return errors.New("not compiled in")
14 +}
fuse/node/mount_test.go new
+98
@@ -0,0 +1,98 @@
1 +// +build !nofuse
2 +
3 +package node
4 +
5 +import (
6 + "io/ioutil"
7 + "os"
8 + "os/exec"
9 + "testing"
10 + "time"
11 +
12 + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
13 + core "github.com/ipfs/go-ipfs/core"
14 + ipns "github.com/ipfs/go-ipfs/fuse/ipns"
15 + mount "github.com/ipfs/go-ipfs/fuse/mount"
16 + namesys "github.com/ipfs/go-ipfs/namesys"
17 + offroute "github.com/ipfs/go-ipfs/routing/offline"
18 + ci "github.com/ipfs/go-ipfs/util/testutil/ci"
19 +)
20 +
21 +func maybeSkipFuseTests(t *testing.T) {
22 + if ci.NoFuse() {
23 + t.Skip("Skipping FUSE tests")
24 + }
25 +}
26 +
27 +func mkdir(t *testing.T, path string) {
28 + err := os.Mkdir(path, os.ModeDir|os.ModePerm)
29 + if err != nil {
30 + t.Fatal(err)
31 + }
32 +}
33 +
34 +// Test externally unmounting, then trying to unmount in code
35 +func TestExternalUnmount(t *testing.T) {
36 + if testing.Short() {
37 + t.SkipNow()
38 + }
39 +
40 + // TODO: needed?
41 + maybeSkipFuseTests(t)
42 +
43 + node, err := core.NewNode(context.Background(), nil)
44 + if err != nil {
45 + t.Fatal(err)
46 + }
47 +
48 + err = node.LoadPrivateKey()
49 + if err != nil {
50 + t.Fatal(err)
51 + }
52 +
53 + node.Routing = offroute.NewOfflineRouter(node.Repo.Datastore(), node.PrivateKey)
54 + node.Namesys = namesys.NewNameSystem(node.Routing, node.Repo.Datastore(), 0)
55 +
56 + err = ipns.InitializeKeyspace(node, node.PrivateKey)
57 + if err != nil {
58 + t.Fatal(err)
59 + }
60 +
61 + // get the test dir paths (/tmp/fusetestXXXX)
62 + dir, err := ioutil.TempDir("", "fusetest")
63 + if err != nil {
64 + t.Fatal(err)
65 + }
66 +
67 + ipfsDir := dir + "/ipfs"
68 + ipnsDir := dir + "/ipns"
69 + mkdir(t, ipfsDir)
70 + mkdir(t, ipnsDir)
71 +
72 + err = Mount(node, ipfsDir, ipnsDir)
73 + if err != nil {
74 + t.Fatal(err)
75 + }
76 +
77 + // Run shell command to externally unmount the directory
78 + cmd := "fusermount"
79 + args := []string{"-u", ipnsDir}
80 + if err := exec.Command(cmd, args...).Run(); err != nil {
81 + t.Fatal(err)
82 + }
83 +
84 + // TODO(noffle): it takes a moment for the goroutine that's running fs.Serve to be notified and do its cleanup.
85 + time.Sleep(time.Millisecond * 100)
86 +
87 + // Attempt to unmount IPNS; check that it was already unmounted.
88 + err = node.Mounts.Ipns.Unmount()
89 + if err != mount.ErrNotMounted {
90 + t.Fatal("Unmount should have failed")
91 + }
92 +
93 + // Attempt to unmount IPFS; it should unmount successfully.
94 + err = node.Mounts.Ipfs.Unmount()
95 + if err != nil {
96 + t.Fatal(err)
97 + }
98 +}
fuse/node/mount_unix.go new
+122
@@ -0,0 +1,122 @@
1 +// +build linux darwin freebsd
2 +// +build !nofuse
3 +
4 +package node
5 +
6 +import (
7 + "errors"
8 + "fmt"
9 + "strings"
10 + "time"
11 +
12 + core "github.com/ipfs/go-ipfs/core"
13 + ipns "github.com/ipfs/go-ipfs/fuse/ipns"
14 + mount "github.com/ipfs/go-ipfs/fuse/mount"
15 + rofs "github.com/ipfs/go-ipfs/fuse/readonly"
16 + logging "gx/ipfs/Qmazh5oNUVsDZTs2g59rq8aYQqwpss8tcUWQzor5sCCEuH/go-log"
17 +)
18 +
19 +var log = logging.Logger("node")
20 +
21 +// amount of time to wait for mount errors
22 +// TODO is this non-deterministic?
23 +const mountTimeout = time.Second
24 +
25 +// fuseNoDirectory used to check the returning fuse error
26 +const fuseNoDirectory = "fusermount: failed to access mountpoint"
27 +
28 +// fuseExitStatus1 used to check the returning fuse error
29 +const fuseExitStatus1 = "fusermount: exit status 1"
30 +
31 +// platformFuseChecks can get overridden by arch-specific files
32 +// to run fuse checks (like checking the OSXFUSE version)
33 +var platformFuseChecks = func(*core.IpfsNode) error {
34 + return nil
35 +}
36 +
37 +func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
38 + // check if we already have live mounts.
39 + // if the user said "Mount", then there must be something wrong.
40 + // so, close them and try again.
41 + if node.Mounts.Ipfs != nil && node.Mounts.Ipfs.IsActive() {
42 + node.Mounts.Ipfs.Unmount()
43 + }
44 + if node.Mounts.Ipns != nil && node.Mounts.Ipns.IsActive() {
45 + node.Mounts.Ipns.Unmount()
46 + }
47 +
48 + if err := platformFuseChecks(node); err != nil {
49 + return err
50 + }
51 +
52 + var err error
53 + if err = doMount(node, fsdir, nsdir); err != nil {
54 + return err
55 + }
56 +
57 + return nil
58 +}
59 +
60 +func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
61 + fmtFuseErr := func(err error, mountpoint string) error {
62 + s := err.Error()
63 + if strings.Contains(s, fuseNoDirectory) {
64 + s = strings.Replace(s, `fusermount: "fusermount:`, "", -1)
65 + s = strings.Replace(s, `\n", exit status 1`, "", -1)
66 + return errors.New(s)
67 + }
68 + if s == fuseExitStatus1 {
69 + s = fmt.Sprintf("fuse failed to access mountpoint %s", mountpoint)
70 + return errors.New(s)
71 + }
72 + return err
73 + }
74 +
75 + // this sync stuff is so that both can be mounted simultaneously.
76 + var fsmount mount.Mount
77 + var nsmount mount.Mount
78 + var err1 error
79 + var err2 error
80 +
81 + done := make(chan struct{})
82 +
83 + go func() {
84 + fsmount, err1 = rofs.Mount(node, fsdir)
85 + done <- struct{}{}
86 + }()
87 +
88 + go func() {
89 + nsmount, err2 = ipns.Mount(node, nsdir, fsdir)
90 + done <- struct{}{}
91 + }()
92 +
93 + <-done
94 + <-done
95 +
96 + if err1 != nil {
97 + log.Errorf("error mounting: %s", err1)
98 + }
99 +
100 + if err2 != nil {
101 + log.Errorf("error mounting: %s", err2)
102 + }
103 +
104 + if err1 != nil || err2 != nil {
105 + if fsmount != nil {
106 + fsmount.Unmount()
107 + }
108 + if nsmount != nil {
109 + nsmount.Unmount()
110 + }
111 +
112 + if err1 != nil {
113 + return fmtFuseErr(err1, fsdir)
114 + }
115 + return fmtFuseErr(err2, nsdir)
116 + }
117 +
118 + // setup node state, so that it can be cancelled
119 + node.Mounts.Ipfs = fsmount
120 + node.Mounts.Ipns = nsmount
121 + return nil
122 +}
fuse/node/mount_windows.go new
+11
@@ -0,0 +1,11 @@
1 +package node
2 +
3 +import (
4 + "github.com/ipfs/go-ipfs/core"
5 +)
6 +
7 +func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
8 + // TODO
9 + // currently a no-op, but we don't want to return an error
10 + return nil
11 +}