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

package commands

import (
	"fmt"
7
	"strings"
8 9 10
	"time"

	cmds "github.com/jbenet/go-ipfs/commands"
11
	config "github.com/jbenet/go-ipfs/config"
12 13
	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 16 17 18
	rofs "github.com/jbenet/go-ipfs/fuse/readonly"
)

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

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

25
var MountCmd = &cmds.Command{
26 27
	Helptext: cmds.HelpText{
		Tagline: "Mounts IPFS to the filesystem (read-only)",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
28
		Synopsis: `
29
ipfs mount [-f <ipfs mount path>] [-n <ipns mount path>]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
30
`,
31
		ShortDescription: `
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
32 33 34 35
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.

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

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
40
> ipfs daemon &
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
41 42 43 44 45 46 47
> 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.

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
50 51
> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
52
> ipfs daemon &
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
53 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
> 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
82
`,
83
	},
84

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

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

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

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

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

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

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

130 131 132 133
		var output config.Mounts
		output.IPFS = fsdir
		output.IPNS = nsdir
		return &output, nil
134 135
	},
	Type: &config.Mounts{},
136
	Marshalers: cmds.MarshalerMap{
137 138 139 140 141 142 143 144 145
		cmds.Text: func(res cmds.Response) ([]byte, error) {
			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)
			return []byte(s), nil
		},
	},
}

146 147 148 149 150 151 152 153 154 155
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
	}
156

157 158 159 160 161
	// 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
162

163
	done := make(chan struct{})
164

165 166 167 168
	go func() {
		fsmount, err1 = rofs.Mount(node, fsdir)
		done <- struct{}{}
	}()
169 170

	go func() {
171 172
		nsmount, err2 = ipns.Mount(node, nsdir, fsdir)
		done <- struct{}{}
173 174
	}()

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
	<-done
	<-done

	if err1 != nil || err2 != nil {
		fsmount.Close()
		nsmount.Close()
		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
192 193 194 195 196
}

var platformFuseChecks = func() error {
	return nil
}
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

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
}