aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/bazil.org/fuse/mount_darwin.go
blob: c1c36e62b5f3204824f437f59fcd7f2eb373ed04 (plain) (tree)















































































































































































































                                                                                                                                   
package fuse

import (
    "errors"
    "fmt"
    "log"
    "os"
    "os/exec"
    "path"
    "strconv"
    "strings"
    "sync"
    "syscall"
)

var (
    errNoAvail   = errors.New("no available fuse devices")
    errNotLoaded = errors.New("osxfuse is not loaded")
)

func loadOSXFUSE(bin string) error {
    cmd := exec.Command(bin)
    cmd.Dir = "/"
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    err := cmd.Run()
    return err
}

func openOSXFUSEDev(devPrefix string) (*os.File, error) {
    var f *os.File
    var err error
    for i := uint64(0); ; i++ {
        path := devPrefix + strconv.FormatUint(i, 10)
        f, err = os.OpenFile(path, os.O_RDWR, 0000)
        if os.IsNotExist(err) {
            if i == 0 {
                // not even the first device was found -> fuse is not loaded
                return nil, errNotLoaded
            }

            // we've run out of kernel-provided devices
            return nil, errNoAvail
        }

        if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY {
            // try the next one
            continue
        }

        if err != nil {
            return nil, err
        }
        return f, nil
    }
}

func handleMountOSXFUSE(helperName string, errCh chan<- error) func(line string) (ignore bool) {
    var noMountpointPrefix = helperName + `: `
    const noMountpointSuffix = `: No such file or directory`
    return func(line string) (ignore bool) {
        if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
            // re-extract it from the error message in case some layer
            // changed the path
            mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
            err := &MountpointDoesNotExistError{
                Path: mountpoint,
            }
            select {
            case errCh <- err:
                return true
            default:
                // not the first error; fall back to logging it
                return false
            }
        }

        return false
    }
}

// isBoringMountOSXFUSEError returns whether the Wait error is
// uninteresting; exit status 64 is.
func isBoringMountOSXFUSEError(err error) bool {
    if err, ok := err.(*exec.ExitError); ok && err.Exited() {
        if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 64 {
            return true
        }
    }
    return false
}

func callMount(bin string, daemonVar string, dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
    for k, v := range conf.options {
        if strings.Contains(k, ",") || strings.Contains(v, ",") {
            // Silly limitation but the mount helper does not
            // understand any escaping. See TestMountOptionCommaError.
            return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v)
        }
    }
    cmd := exec.Command(
        bin,
        "-o", conf.getOptions(),
        // Tell osxfuse-kext how large our buffer is. It must split
        // writes larger than this into multiple writes.
        //
        // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
        // this instead.
        "-o", "iosize="+strconv.FormatUint(maxWrite, 10),
        // refers to fd passed in cmd.ExtraFiles
        "3",
        dir,
    )
    cmd.ExtraFiles = []*os.File{f}
    cmd.Env = os.Environ()
    // OSXFUSE <3.3.0
    cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
    // 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)
    }

    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
    }
    stderr, err := cmd.StderrPipe()
    if err != nil {
        return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
    }

    if err := cmd.Start(); err != nil {
        return fmt.Errorf("mount_osxfusefs: %v", err)
    }
    helperErrCh := make(chan error, 1)
    go func() {
        var wg sync.WaitGroup
        wg.Add(2)
        go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
        helperName := path.Base(bin)
        go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, helperErrCh), stderr)
        wg.Wait()
        if err := cmd.Wait(); err != nil {
            // see if we have a better error to report
            select {
            case helperErr := <-helperErrCh:
                // log the Wait error if it's not what we expected
                if !isBoringMountOSXFUSEError(err) {
                    log.Printf("mount helper failed: %v", err)
                }
                // and now return what we grabbed from stderr as the real
                // error
                *errp = helperErr
                close(ready)
                return
            default:
                // nope, fall back to generic message
            }

            *errp = fmt.Errorf("mount_osxfusefs: %v", err)
            close(ready)
            return
        }

        *errp = nil
        close(ready)
    }()
    return nil
}

func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
    locations := conf.osxfuseLocations
    if locations == nil {
        locations = []OSXFUSEPaths{
            OSXFUSELocationV3,
            OSXFUSELocationV2,
        }
    }
    for _, loc := range locations {
        if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
            // try the other locations
            continue
        }

        f, err := openOSXFUSEDev(loc.DevicePrefix)
        if err == errNotLoaded {
            err = loadOSXFUSE(loc.Load)
            if err != nil {
                return nil, err
            }
            // try again
            f, err = openOSXFUSEDev(loc.DevicePrefix)
        }
        if err != nil {
            return nil, err
        }
        err = callMount(loc.Mount, loc.DaemonVar, dir, conf, f, ready, errp)
        if err != nil {
            f.Close()
            return nil, err
        }
        return f, nil
    }
    return nil, ErrOSXFUSENotFound
}