From 7ae3706b6d8c13e61b2f829e7296fc471401ce2d Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Wed, 28 Jan 2015 05:18:41 -0800 Subject: [PATCH] fuse: no longer require fuse to compile ipfs This commit removes the dependency on go-fuse-version, and thus the fuse headers. It also introduces an elaborate troubleshooting process that diagnoses whether fuse installed -- and which version -- with as little requirements as possible (attept to use sysctl, fall-back on the go-fuse-version binary, etc). It then nicely instructs the user what to do next. --- Godeps/Godeps.json | 2 +- .../jbenet/go-fuse-version/README.md | 10 +- .../go-fuse-version/fuse-version/.gitignore | 1 + .../{fuseprint => fuse-version}/README.md | 0 .../go-fuse-version/fuse-version/index.go | 106 +++++++++ .../go-fuse-version/fuseprint/.gitignore | 1 - .../jbenet/go-fuse-version/fuseprint/index.go | 21 -- .../jbenet/go-fuse-version/version.go | 2 +- .../jbenet/go-fuse-version/version_bsd.go | 2 +- .../jbenet/go-fuse-version/version_darwin.go | 6 +- .../jbenet/go-fuse-version/version_linux.go | 2 +- .../jbenet/go-fuse-version/version_windows.go | 2 +- .../internal/incfusever/incfusever.go | 13 ++ core/commands/mount.go | 9 - core/commands/mount_darwin.go | 209 ++++++++++++++++-- core/commands/mount_unix.go | 12 +- 16 files changed, 326 insertions(+), 72 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/.gitignore rename Godeps/_workspace/src/github.com/jbenet/go-fuse-version/{fuseprint => fuse-version}/README.md (100%) create mode 100644 Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/index.go delete mode 100644 Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/.gitignore delete mode 100644 Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/index.go create mode 100644 core/commands/internal/incfusever/incfusever.go delete mode 100644 core/commands/mount.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a38a9eec740..cb6f27ac843 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -147,7 +147,7 @@ }, { "ImportPath": "github.com/jbenet/go-fuse-version", - "Rev": "c723f93ceeb1d1e21eb7fe6fd39aa21a9fe7db99" + "Rev": "b733dfc0597e1f6780510ee7afad8b6e3c7af3eb" }, { "ImportPath": "github.com/jbenet/go-is-domain", diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/README.md b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/README.md index e03afc0c7b4..ec2e384b69a 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/README.md +++ b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/README.md @@ -32,14 +32,14 @@ func main() { } ``` -## fuseprint +## fuse-print -If you dont use Go, you can also install the example as the silly util fuseprint: +If you dont use Go, you can also install the example as the silly util `fuse-version`: ``` -> go get github.com/jbenet/go-fuse-version/fuseprint -> go install github.com/jbenet/go-fuse-version/fuseprint -> fuseprint +> go get github.com/jbenet/go-fuse-version/fuse-version +> go install github.com/jbenet/go-fuse-version/fuse-version +> fuse-version FuseVersion, AgentVersion, Agent 27, 2.7.2, OSXFUSE ``` diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/.gitignore b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/.gitignore new file mode 100644 index 00000000000..895cfcff7a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/.gitignore @@ -0,0 +1 @@ +fuse-version diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/README.md b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/README.md similarity index 100% rename from Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/README.md rename to Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/README.md diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/index.go b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/index.go new file mode 100644 index 00000000000..4f909681d30 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuse-version/index.go @@ -0,0 +1,106 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "os" + + fuseversion "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-fuse-version" +) + +// flags +var ( + flagSystem string + flagOnly string + flagQuiet bool +) + +var usage = `usage: %s [flags] +print fuse and fuse agent versions +` + +func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, usage, os.Args[0]) + flag.PrintDefaults() + } + + flag.StringVar(&flagSystem, "s", "", "show only one system (e.g. OSXFUSE)") + flag.StringVar(&flagOnly, "only", "", "show one of {fuse, agent, agent-name}") + flag.BoolVar(&flagQuiet, "q", false, "quiet output, no newline (use with --only)") +} + +func main() { + flag.Parse() + sys, err := fuseversion.LocalFuseSystems() + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } + + all := flagSystem == "" + + // if user specified a system and we dont have it, error out. + if !all { + checkExists(sys, flagSystem) + } + + var buf bytes.Buffer + for name, s := range sys { + if !all && flagSystem != name { + continue + } + + switch flagOnly { + case "fuse": + fmt.Fprintf(&buf, PartString(name, "FuseVersion", s.FuseVersion)) + case "agent": + fmt.Fprintf(&buf, PartString(name, "AgentVersion", s.AgentVersion)) + case "agent-name": + fmt.Fprintf(&buf, PartString(name, "AgentName", s.AgentName)) + default: + fmt.Fprintf(&buf, SystemString(name, s)) + } + + if all && flagQuiet { // if all & quiet, need to break between systems + fmt.Fprintf(&buf, "\n") + } + } + + out := buf.Bytes() + if flagQuiet { + out = bytes.TrimSpace(out) + } + os.Stdout.Write(out) +} + +func checkExists(all fuseversion.Systems, name string) { + + if _, found := all[name]; found { + return + } + + if !flagQuiet { + fmt.Fprintf(os.Stderr, "error: %s system not found.\nHave: ") + for name := range all { + fmt.Fprintf(os.Stderr, "%s ", name) + } + fmt.Fprintf(os.Stderr, "\n") + } + os.Exit(1) +} + +func SystemString(name string, sys fuseversion.FuseSystem) (s string) { + s += PartString(name, "FuseVersion", sys.FuseVersion) + s += PartString(name, "AgentVersion", sys.AgentVersion) + s += PartString(name, "AgentName", sys.AgentName) + return s +} + +func PartString(sysname, partname, part string) string { + if !flagQuiet { + return fmt.Sprintf("%s.%s: %s\n", sysname, partname, part) + } + return part + "\t" +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/.gitignore b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/.gitignore deleted file mode 100644 index 4778de0e4bb..00000000000 --- a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/.gitignore +++ /dev/null @@ -1 +0,0 @@ -fuseprint diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/index.go b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/index.go deleted file mode 100644 index 8e4e04aa6a4..00000000000 --- a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/fuseprint/index.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "fmt" - "os" - - fuseversion "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-fuse-version" -) - -func main() { - sys, err := fuseversion.LocalFuseSystems() - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(1) - } - - fmt.Printf("FuseVersion, AgentVersion, Agent\n") - for _, s := range *sys { - fmt.Printf("%s, %s, %s\n", s.FuseVersion, s.AgentVersion, s.AgentName) - } -} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version.go b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version.go index b46bd7db493..d91ef21a789 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version.go @@ -29,7 +29,7 @@ type FuseSystem struct { // // Outputs: // // OSXFUSE, , 2.7.2 // -func LocalFuseSystems() (*Systems, error) { +func LocalFuseSystems() (Systems, error) { return getLocalFuseSystems() // implemented by each platform } diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_bsd.go b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_bsd.go index 850fb1e4b39..18600738089 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_bsd.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_bsd.go @@ -7,6 +7,6 @@ import ( "runtime" ) -func getLocalFuseSystems() (*Systems, error) { +func getLocalFuseSystems() (Systems, error) { return nil, fmt.Errorf(notImplYet, runtime.GOARCH) } diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_darwin.go b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_darwin.go index 84325578ba9..fab03403b9b 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_darwin.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_darwin.go @@ -9,10 +9,10 @@ package fuseversion import "C" import "fmt" -func getLocalFuseSystems() (*Systems, error) { - sys := Systems{} +func getLocalFuseSystems() (Systems, error) { + sys := make(Systems) sys["OSXFUSE"] = getOSXFUSE() - return &sys, nil + return sys, nil } func getOSXFUSE() FuseSystem { diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_linux.go b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_linux.go index 6ad4db59881..c85d3cd5285 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_linux.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_linux.go @@ -5,6 +5,6 @@ import ( "runtime" ) -func getLocalFuseSystems() (*Systems, error) { +func getLocalFuseSystems() (Systems, error) { return nil, fmt.Errorf(notImplYet, runtime.GOARCH) } diff --git a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_windows.go b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_windows.go index 6ad4db59881..c85d3cd5285 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_windows.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-fuse-version/version_windows.go @@ -5,6 +5,6 @@ import ( "runtime" ) -func getLocalFuseSystems() (*Systems, error) { +func getLocalFuseSystems() (Systems, error) { return nil, fmt.Errorf(notImplYet, runtime.GOARCH) } diff --git a/core/commands/internal/incfusever/incfusever.go b/core/commands/internal/incfusever/incfusever.go new file mode 100644 index 00000000000..9d8a0f17fed --- /dev/null +++ b/core/commands/internal/incfusever/incfusever.go @@ -0,0 +1,13 @@ +// Package incfusever is only here to prevent go src tools (like godep) +// from thinking fuseversion is not a required package. Though we do not +// actually use github.com/jbenet/go-fuse-version as a library, we +// _may_ need its binary. We avoid it as much as possible as compiling +// it _requires_ fuse headers. Users must be able to install go-ipfs +// without also installing fuse. +package incfusever + +import ( + fuseversion "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-fuse-version" +) + +var _ = fuseversion.LocalFuseSystems diff --git a/core/commands/mount.go b/core/commands/mount.go deleted file mode 100644 index 5c38e1a2552..00000000000 --- a/core/commands/mount.go +++ /dev/null @@ -1,9 +0,0 @@ -package commands - -import ( - fuseversion "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-fuse-version" -) - -// this file is only here to prevent go src tools (like godep) from -// thinking fuseversion is not a required package by non-darwin archs. -var _ = fuseversion.LocalFuseSystems diff --git a/core/commands/mount_darwin.go b/core/commands/mount_darwin.go index f98c56de0c6..c9eabb4b002 100644 --- a/core/commands/mount_darwin.go +++ b/core/commands/mount_darwin.go @@ -1,12 +1,14 @@ package commands import ( + "bytes" "fmt" + "os/exec" "runtime" "strings" "syscall" - fuseversion "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-fuse-version" + core "github.com/jbenet/go-ipfs/core" ) func init() { @@ -14,19 +16,122 @@ func init() { platformFuseChecks = darwinFuseCheckVersion } -func darwinFuseCheckVersion() error { +// 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 ` 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 { // on OSX, check FUSE version. if runtime.GOOS != "darwin" { return nil } - ov, err := tryGFV() - if err != nil { - log.Debug(err) - ov, err = trySysctl() - if err != nil { - log.Debug(err) - return fmt.Errorf("cannot determine osxfuse version. is it installed?") + 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 } } @@ -35,25 +140,18 @@ func darwinFuseCheckVersion() error { return nil } - return fmt.Errorf("osxfuse version %s not supported.\n%s\n%s", ov, - "Older versions of osxfuse have kernel panic bugs; please upgrade!", - "https://github.com/jbenet/go-ipfs/issues/177") + return fmt.Errorf(errStrUpgradeFuse, ov) } func tryGFV() (string, error) { - sys, err := fuseversion.LocalFuseSystems() - if err != nil { - log.Debug("mount: fuseversion:", "failed") - return "", err - } - - for _, s := range *sys { - v := s.AgentVersion - log.Debug("mount: fuseversion:", v) - return v, nil + // first try sysctl. it may work! + ov, err := trySysctl() + if err == nil { + return ov, nil } + log.Debug(err) - return "", fmt.Errorf("fuseversion: no system found") + return tryGFVFromFuseVersion() } func trySysctl() (string, error) { @@ -65,3 +163,68 @@ func trySysctl() (string, error) { log.Debug("mount: sysctl osxfuse.version.number:", v) return v, nil } + +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 +} diff --git a/core/commands/mount_unix.go b/core/commands/mount_unix.go index 96646317602..5db9f815063 100644 --- a/core/commands/mount_unix.go +++ b/core/commands/mount_unix.go @@ -23,6 +23,12 @@ const mountTimeout = time.Second // fuseNoDirectory used to check the returning fuse error const fuseNoDirectory = "fusermount: failed to access mountpoint" +// platformFuseChecks can get overridden by arch-specific files +// to run fuse checks (like checking the OSXFUSE version) +var platformFuseChecks = func(*core.IpfsNode) error { + return nil +} + var MountCmd = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Mounts IPFS to the filesystem (read-only)", @@ -161,7 +167,7 @@ func Mount(node *core.IpfsNode, fsdir, nsdir string) error { node.Mounts.Ipns.Unmount() } - if err := platformFuseChecks(); err != nil { + if err := platformFuseChecks(node); err != nil { return err } @@ -173,10 +179,6 @@ func Mount(node *core.IpfsNode, fsdir, nsdir string) error { return nil } -var platformFuseChecks = func() error { - return nil -} - func doMount(node *core.IpfsNode, fsdir, nsdir string) error { fmtFuseErr := func(err error) error { s := err.Error()