diff --git a/mount_config.go b/mount_config.go index 6828ed3..45d9546 100644 --- a/mount_config.go +++ b/mount_config.go @@ -135,6 +135,14 @@ type MountConfig struct { // default name involving the string 'osxfuse' is used. VolumeName string + // OS X only. + // + // OSXFUSELocations sets where to look for OSXFUSE files. The arguments are + // all the possible locations. The previous locations are replaced. + // + // Without this option, OSXFUSELocationV3 and OSXFUSELocationV2 are used. + OSXFUSELocations []OSXFUSEPaths + // Additional key=value options to pass unadulterated to the underlying mount // command. See `man 8 mount`, the fuse documentation, etc. for // system-specific information. @@ -230,3 +238,38 @@ func (c *MountConfig) toOptionsString() string { return strings.Join(components, ",") } + +// OSXFUSEPaths describes the paths used by an installed OSXFUSE version. +// See OSXFUSELocationV3 for typical values. +type OSXFUSEPaths struct { + // Prefix for the device file. At mount time, an incrementing number is + // suffixed until a free FUSE device is found. + DevicePrefix string + + // Path of the load helper, used to load the kernel extension if no device + // files are found. + Load string + + // Path of the mount helper, used for the actual mount operation. + Mount string + + // Environment variable used to pass the path to the executable calling the + // mount helper. + DaemonVar string +} + +// Default paths for OSXFUSE. See OSXFUSELocations. +var ( + OSXFUSELocationV3 = OSXFUSEPaths{ + DevicePrefix: "/dev/osxfuse", + Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse", + Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse", + DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH", + } + OSXFUSELocationV2 = OSXFUSEPaths{ + DevicePrefix: "/dev/osxfuse", + Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs", + Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs", + DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH", + } +) diff --git a/mount_darwin.go b/mount_darwin.go index 8ecf640..97dc1a1 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -14,10 +14,16 @@ import ( ) var errNoAvail = errors.New("no available fuse devices") -var errNotLoaded = errors.New("osxfusefs is not loaded") +var errNotLoaded = errors.New("osxfuse is not loaded") -func loadOSXFUSE() error { - cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs") +// errOSXFUSENotFound is returned from Mount when the OSXFUSE installation is +// not detected. +// +// Make sure OSXFUSE is installed, or see OSXFUSELocations for customization. +var errOSXFUSENotFound = errors.New("cannot locate OSXFUSE") + +func loadOSXFUSE(bin string) error { + cmd := exec.Command(bin) cmd.Dir = "/" cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -25,10 +31,10 @@ func loadOSXFUSE() error { return err } -func openOSXFUSEDev() (dev *os.File, err error) { +func openOSXFUSEDev(devPrefix string) (dev *os.File, err error) { // Try each device name. for i := uint64(0); ; i++ { - path := fmt.Sprintf("/dev/osxfuse%d", i) + path := devPrefix + strconv.FormatUint(i, 10) dev, err = os.OpenFile(path, os.O_RDWR, 0000) if os.IsNotExist(err) { if i == 0 { @@ -52,11 +58,12 @@ func openOSXFUSEDev() (dev *os.File, err error) { } func callMount( + bin string, + daemonVar string, dir string, cfg *MountConfig, dev *os.File, ready chan<- error) (err error) { - const bin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" // The mount helper doesn't understand any escaping. for k, v := range cfg.toMap() { @@ -85,8 +92,15 @@ func callMount( ) cmd.ExtraFiles = []*os.File{dev} cmd.Env = os.Environ() + // OSXFUSE <3.3.0 cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") - cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin) + // OSXFUSE >=3.3.0 + cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=") + + daemon := os.Args[0] + if daemonVar != "" { + cmd.Env = append(cmd.Env, daemonVar+"="+daemon) + } var buf bytes.Buffer cmd.Stdout = &buf @@ -122,34 +136,53 @@ func mount( dir string, cfg *MountConfig, ready chan<- error) (dev *os.File, err error) { - // Open the device. - dev, err = openOSXFUSEDev() + // get OSXFUSE locations + locations := cfg.OSXFUSELocations + if locations == nil { + locations = []OSXFUSEPaths{ + OSXFUSELocationV3, + OSXFUSELocationV2, + } + } - // Special case: we may need to explicitly load osxfuse. Load it, then try - // again. - if err == errNotLoaded { - err = loadOSXFUSE() + for _, loc := range locations { + if _, err := os.Stat(loc.Mount); os.IsNotExist(err) { + // try the other locations + continue + } + + // Open the device. + dev, err = openOSXFUSEDev(loc.DevicePrefix) + + // Special case: we may need to explicitly load osxfuse. Load it, then + // try again. + if err == errNotLoaded { + err = loadOSXFUSE(loc.Load) + if err != nil { + err = fmt.Errorf("loadOSXFUSE: %v", err) + return + } + + dev, err = openOSXFUSEDev(loc.DevicePrefix) + } + + // Propagate errors. if err != nil { - err = fmt.Errorf("loadOSXFUSE: %v", err) + err = fmt.Errorf("openOSXFUSEDev: %v", err) return } - dev, err = openOSXFUSEDev() - } + // Call the mount binary with the device. + err = callMount(loc.Mount, loc.DaemonVar, dir, cfg, dev, ready) + if err != nil { + dev.Close() + err = fmt.Errorf("callMount: %v", err) + return + } - // Propagate errors. - if err != nil { - err = fmt.Errorf("openOSXFUSEDev: %v", err) - return - } - - // Call the mount binary with the device. - err = callMount(dir, cfg, dev, ready) - if err != nil { - dev.Close() - err = fmt.Errorf("callMount: %v", err) return } + err = errOSXFUSENotFound return }