mount_unix.go 5.17 KB
Newer Older
1 2 3 4 5 6
// +build linux darwin freebsd

package commands

import (
	"fmt"
7
	"io"
8
	"strings"
9 10 11 12 13
	"time"

	cmds "github.com/jbenet/go-ipfs/commands"
	core "github.com/jbenet/go-ipfs/core"
	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
	config "github.com/jbenet/go-ipfs/repo/config"
17 18 19
)

// amount of time to wait for mount errors
20
// TODO is this non-deterministic?
21 22
const mountTimeout = time.Second

23 24 25
// fuseNoDirectory used to check the returning fuse error
const fuseNoDirectory = "fusermount: failed to access mountpoint"

26
var MountCmd = &cmds.Command{
27 28
	Helptext: cmds.HelpText{
		Tagline: "Mounts IPFS to the filesystem (read-only)",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
29
		Synopsis: `
30
ipfs mount [-f <ipfs mount path>] [-n <ipns mount path>]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
31
`,
32
		ShortDescription: `
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
33 34 35 36
Mount ipfs at a read-only mountpoint on the OS (default: /ipfs and /ipns).
All ipfs objects will be accessible under that directory. Note that the
root will not be listable, as it is virtual. Access known paths directly.

37
You may have to create /ipfs and /ipfs before using 'ipfs mount':
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
38 39 40

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
41
> ipfs daemon &
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
42 43 44 45 46 47 48
> ipfs mount
`,
		LongDescription: `
Mount ipfs at a read-only mountpoint on the OS (default: /ipfs and /ipns).
All ipfs objects will be accessible under that directory. Note that the
root will not be listable, as it is virtual. Access known paths directly.

49 50
You may have to create /ipfs and /ipfs before using 'ipfs mount':

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
51 52
> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
53
> ipfs daemon &
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
> ipfs mount

EXAMPLE:

# setup
> mkdir foo
> echo "baz" > foo/bar
> ipfs add -r foo
added QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR foo/bar
added QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC foo
> ipfs ls QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR 12 bar
> ipfs cat QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
baz

# mount
> ipfs daemon &
> ipfs mount
IPFS mounted at: /ipfs
IPNS mounted at: /ipns
> cd /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
> ls
bar
> cat bar
baz
> cat /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC/bar
baz
> cat /ipfs/QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
baz
83
`,
84
	},
85

86
	Options: []cmds.Option{
Brian Tiger Chow's avatar
Brian Tiger Chow committed
87
		// TODO longform
88
		cmds.StringOption("f", "The path where IPFS should be mounted"),
Brian Tiger Chow's avatar
Brian Tiger Chow committed
89 90

		// TODO longform
91
		cmds.StringOption("n", "The path where IPNS should be mounted"),
92
	},
93
	Run: func(req cmds.Request) (interface{}, error) {
94 95 96 97 98 99 100 101 102
		cfg, err := req.Context().GetConfig()
		if err != nil {
			return nil, err
		}

		node, err := req.Context().GetNode()
		if err != nil {
			return nil, err
		}
103

104
		// error if we aren't running node in online mode
105
		if !node.OnlineMode() {
106
			return nil, errNotOnline
107 108
		}

109 110 111 112 113
		fsdir, found, err := req.Option("f").String()
		if err != nil {
			return nil, err
		}
		if !found {
114
			fsdir = cfg.Mounts.IPFS // use default value
115 116 117
		}

		// get default mount points
118 119 120
		nsdir, found, err := req.Option("n").String()
		if err != nil {
			return nil, err
121
		}
122
		if !found {
123
			nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare!
124 125
		}

126 127
		err = Mount(node, fsdir, nsdir)
		if err != nil {
128
			return nil, err
129 130
		}

131 132 133 134
		var output config.Mounts
		output.IPFS = fsdir
		output.IPNS = nsdir
		return &output, nil
135
	},
136
	Type: config.Mounts{},
137
	Marshalers: cmds.MarshalerMap{
138
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
139 140 141
			v := res.Output().(*config.Mounts)
			s := fmt.Sprintf("IPFS mounted at: %s\n", v.IPFS)
			s += fmt.Sprintf("IPNS mounted at: %s\n", v.IPNS)
142
			return strings.NewReader(s), nil
143 144 145 146
		},
	},
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
	// check if we already have live mounts.
	// if the user said "Mount", then there must be something wrong.
	// so, close them and try again.
	if node.Mounts.Ipfs != nil {
		node.Mounts.Ipfs.Unmount()
	}
	if node.Mounts.Ipns != nil {
		node.Mounts.Ipns.Unmount()
	}

	if err := platformFuseChecks(); err != nil {
		return err
	}

	var err error
	if err = doMount(node, fsdir, nsdir); err != nil {
		return err
	}

	return nil
}

var platformFuseChecks = func() error {
	return nil
}

174 175 176 177 178 179 180 181 182 183
func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
	fmtFuseErr := func(err error) error {
		s := err.Error()
		if strings.Contains(s, fuseNoDirectory) {
			s = strings.Replace(s, `fusermount: "fusermount:`, "", -1)
			s = strings.Replace(s, `\n", exit status 1`, "", -1)
			return cmds.ClientError(s)
		}
		return err
	}
184

185 186 187 188 189
	// this sync stuff is so that both can be mounted simultaneously.
	var fsmount mount.Mount
	var nsmount mount.Mount
	var err1 error
	var err2 error
190

191
	done := make(chan struct{})
192

193 194 195 196
	go func() {
		fsmount, err1 = rofs.Mount(node, fsdir)
		done <- struct{}{}
	}()
197 198

	go func() {
199 200
		nsmount, err2 = ipns.Mount(node, nsdir, fsdir)
		done <- struct{}{}
201 202
	}()

203 204 205 206
	<-done
	<-done

	if err1 != nil || err2 != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
207 208 209 210 211 212 213 214
		log.Infof("error mounting: %s %s", err1, err2)
		if fsmount != nil {
			fsmount.Unmount()
		}
		if nsmount != nil {
			nsmount.Unmount()
		}

215 216 217 218 219 220 221 222 223 224 225
		if err1 != nil {
			return fmtFuseErr(err1)
		} else {
			return fmtFuseErr(err2)
		}
	}

	// setup node state, so that it can be cancelled
	node.Mounts.Ipfs = fsmount
	node.Mounts.Ipns = nsmount
	return nil
226
}