mount_darwin.go 6.44 KB
Newer Older
1 2
// +build !nofuse

3 4 5
package commands

import (
6
	"bytes"
7
	"fmt"
8
	"os/exec"
9 10 11
	"runtime"
	"strings"
	"syscall"
12

13
	core "github.com/jbenet/go-ipfs/core"
14 15 16 17 18 19 20
)

func init() {
	// this is a hack, but until we need to do it another way, this works.
	platformFuseChecks = darwinFuseCheckVersion
}

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
// dontCheckOSXFUSEConfigKey is a key used to let the user tell us to
// skip fuse checks.
var dontCheckOSXFUSEConfigKey = "DontCheckOSXFUSE"

// fuseVersionPkg is the go pkg url for fuse-version
var fuseVersionPkg = "github.com/jbenet/go-fuse-version/fuse-version"

// errStrFuseRequired is returned when we're sure the user does not have fuse.
var errStrFuseRequired = `OSXFUSE not found.

OSXFUSE is required to mount, please install it.
Note: version 2.7.2 or higher required; prior versions are known to kernel panic!
It is recommended you install it from the OSXFUSE website:

	http://osxfuse.github.io/

For more help, see:

	https://github.com/jbenet/go-ipfs/issues/177
`

// errStrNoFuseHeaders is included in the output of `go get <fuseVersionPkg>` if there
// are no fuse headers. this means they dont have OSXFUSE installed.
var errStrNoFuseHeaders = "no such file or directory: '/usr/local/lib/libosxfuse.dylib'"

var errStrUpgradeFuse = `OSXFUSE version %s not supported.

OSXFUSE versions <2.7.2 are known to cause kernel panics!
Please upgrade to the latest OSXFUSE version.
It is recommended you install it from the OSXFUSE website:

	http://osxfuse.github.io/

For more help, see:

	https://github.com/jbenet/go-ipfs/issues/177
`

var errStrNeedFuseVersion = `unable to check fuse version.

Dear User,

Before mounting, we must check your version of OSXFUSE. We are protecting
you from a nasty kernel panic we found in OSXFUSE versions <2.7.2.[1]. To
make matters worse, it's harder than it should be to check whether you have
the right version installed...[2]. We've automated the process with the
help of a little tool. We tried to install it, but something went wrong[3].
Please install it yourself by running:

	go get %s

You can also stop ipfs from running these checks and use whatever OSXFUSE
version you have by running:

	ipfs config %s true

[1]: https://github.com/jbenet/go-ipfs/issues/177
[2]: https://github.com/jbenet/go-ipfs/pull/533
[3]: %s
`

var errStrFailedToRunFuseVersion = `unable to check fuse version.

Dear User,

Before mounting, we must check your version of OSXFUSE. We are protecting
you from a nasty kernel panic we found in OSXFUSE versions <2.7.2.[1]. To
make matters worse, it's harder than it should be to check whether you have
the right version installed...[2]. We've automated the process with the
help of a little tool. We tried to run it, but something went wrong[3].
Please, try to run it yourself with:

	go get %s
	fuse-version

You should see something like this:

	> fuse-version
	fuse-version -only agent
	OSXFUSE.AgentVersion: 2.7.3

Just make sure the number is 2.7.2 or higher. You can then stop ipfs from
trying to run these checks with:

	ipfs config %s true

[1]: https://github.com/jbenet/go-ipfs/issues/177
[2]: https://github.com/jbenet/go-ipfs/pull/533
[3]: %s
`

var errStrFixConfig = `config key invalid: %s %s
You may be able to get this error to go away by setting it again:

	ipfs config %s true

Either way, please tell us at: http://github.com/jbenet/go-ipfs/issues
`

func darwinFuseCheckVersion(node *core.IpfsNode) error {
121 122 123 124 125
	// on OSX, check FUSE version.
	if runtime.GOOS != "darwin" {
		return nil
	}

126 127 128 129 130 131 132 133 134 135 136
	ov, errGFV := tryGFV()
	if errGFV != nil {
		// if we failed AND the user has told us to ignore the check we
		// continue. this is in case fuse-version breaks or the user cannot
		// install it, but is sure their fuse version will work.
		if skip, err := userAskedToSkipFuseCheck(node); err != nil {
			return err
		} else if skip {
			return nil // user told us not to check version... ok....
		} else {
			return errGFV
137
		}
138 139
	}

140
	log.Debug("mount: osxfuse version:", ov)
141 142 143 144
	if strings.HasPrefix(ov, "2.7.") || strings.HasPrefix(ov, "2.8.") {
		return nil
	}

145
	return fmt.Errorf(errStrUpgradeFuse, ov)
146
}
147 148

func tryGFV() (string, error) {
149 150 151 152
	// first try sysctl. it may work!
	ov, err := trySysctl()
	if err == nil {
		return ov, nil
153
	}
154
	log.Debug(err)
155

156
	return tryGFVFromFuseVersion()
157 158 159 160 161 162 163 164 165 166 167
}

func trySysctl() (string, error) {
	v, err := syscall.Sysctl("osxfuse.version.number")
	if err != nil {
		log.Debug("mount: sysctl osxfuse.version.number:", "failed")
		return "", err
	}
	log.Debug("mount: sysctl osxfuse.version.number:", v)
	return v, nil
}
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 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

func tryGFVFromFuseVersion() (string, error) {
	if err := ensureFuseVersionIsInstalled(); err != nil {
		return "", err
	}

	cmd := exec.Command("fuse-version", "-q", "-only", "agent", "-s", "OSXFUSE")
	var out bytes.Buffer
	cmd.Stdout = &out
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf(errStrFailedToRunFuseVersion, fuseVersionPkg, dontCheckOSXFUSEConfigKey, err)
	}

	return out.String(), nil
}

func ensureFuseVersionIsInstalled() error {
	// see if fuse-version is there
	if _, err := exec.LookPath("fuse-version"); err == nil {
		return nil // got it!
	}

	// try installing it...
	log.Debug("fuse-version: no fuse-version. attempting to install.")
	cmd := exec.Command("go", "get", "github.com/jbenet/go-fuse-version/fuse-version")
	var cmdout bytes.Buffer
	cmd.Stdout = &cmdout
	cmd.Stderr = &cmdout
	if err := cmd.Run(); err != nil {
		// Ok, install fuse-version failed. is it they dont have fuse?
		cmdoutstr := cmdout.String()
		if strings.Contains(cmdoutstr, errStrNoFuseHeaders) {
			// yes! it is! they dont have fuse!
			return fmt.Errorf(errStrFuseRequired)
		}

		log.Debug("fuse-version: failed to install.")
		s := err.Error() + "\n" + cmdoutstr
		return fmt.Errorf(errStrNeedFuseVersion, fuseVersionPkg, dontCheckOSXFUSEConfigKey, s)
	}

	// ok, try again...
	if _, err := exec.LookPath("fuse-version"); err != nil {
		log.Debug("fuse-version: failed to install?")
		return fmt.Errorf(errStrNeedFuseVersion, fuseVersionPkg, dontCheckOSXFUSEConfigKey, err)
	}

	log.Debug("fuse-version: install success")
	return nil
}

func userAskedToSkipFuseCheck(node *core.IpfsNode) (skip bool, err error) {
	val, err := node.Repo.GetConfigKey(dontCheckOSXFUSEConfigKey)
	if err != nil {
		return false, nil // failed to get config value. dont skip check.
	}
	s, ok := val.(string)
	if !ok {
		// got config value, but it's invalid... dont skip check, ask the user to fix it...
		return false, fmt.Errorf(errStrFixConfig, dontCheckOSXFUSEConfigKey, val,
			dontCheckOSXFUSEConfigKey)
	}
	// only "true" counts as telling us to skip.
	return s == "true", nil
}