mount_unix.go 4.58 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 14 15 16 17
	core "github.com/jbenet/go-ipfs/core"
	ipns "github.com/jbenet/go-ipfs/fuse/ipns"
	rofs "github.com/jbenet/go-ipfs/fuse/readonly"
)

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

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

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

35
You may have to create /ipfs and /ipfs before using 'ipfs mount':
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
36 37 38 39 40 41 42 43 44 45

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
> 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.

46 47
You may have to create /ipfs and /ipfs before using 'ipfs mount':

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
48 49 50 51 52 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
> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
> 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
79
`,
80
	},
81

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

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

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

		// error if we aren't running node in online mode
101
		if node.Network == nil {
102
			return nil, errNotOnline
103 104 105
		}

		if err := platformFuseChecks(); err != nil {
106
			return nil, err
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
		fsdone := mountIpfs(node, fsdir)
117 118

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

127
		nsdone := mountIpns(node, nsdir, fsdir)
128

129 130 131 132 133 134 135 136 137 138
		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
		}

139 140
		// wait until mounts return an error (or timeout if successful)
		select {
141
		case err := <-fsdone:
142
			return nil, fmtFuseErr(err)
143
		case err := <-nsdone:
144
			return nil, fmtFuseErr(err)
145 146 147

		// mounted successfully, we timed out with no errors
		case <-time.After(mountTimeout):
148
			output := cfg.Mounts
149
			return &output, nil
150 151 152
		}
	},
	Type: &config.Mounts{},
153
	Marshalers: cmds.MarshalerMap{
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
		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
		},
	},
}

func mountIpfs(node *core.IpfsNode, fsdir string) <-chan error {
	done := make(chan error)
	log.Info("Mounting IPFS at ", fsdir)

	go func() {
		err := rofs.Mount(node, fsdir)
		done <- err
		close(done)
	}()

	return done
}

func mountIpns(node *core.IpfsNode, nsdir, fsdir string) <-chan error {
	if nsdir == "" {
		return nil
	}
	done := make(chan error)
	log.Info("Mounting IPNS at ", nsdir)

	go func() {
		err := ipns.Mount(node, nsdir, fsdir)
		done <- err
		close(done)
	}()

	return done
}

var platformFuseChecks = func() error {
	return nil
}