aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/bazil.org/fuse/mount_linux.go
blob: 197d1044e926daa8cd48b61992fcceeaac60e8e7 (plain) (tree)





















































































































































                                                                                                               
package fuse

import (
    "fmt"
    "log"
    "net"
    "os"
    "os/exec"
    "strings"
    "sync"
    "syscall"
)

func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) {
    return func(line string) (ignore bool) {
        if line == `fusermount: failed to open /etc/fuse.conf: Permission denied` {
            // Silence this particular message, it occurs way too
            // commonly and isn't very relevant to whether the mount
            // succeeds or not.
            return true
        }

        const (
            noMountpointPrefix = `fusermount: failed to access mountpoint `
            noMountpointSuffix = `: No such file or directory`
        )
        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
    }
}

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

func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) {
    // linux mount is never delayed
    close(ready)

    fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
    if err != nil {
        return nil, fmt.Errorf("socketpair error: %v", err)
    }

    writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
    defer writeFile.Close()

    readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
    defer readFile.Close()

    cmd := exec.Command(
        "fusermount",
        "-o", conf.getOptions(),
        "--",
        dir,
    )
    cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")

    cmd.ExtraFiles = []*os.File{writeFile}

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

    if err := cmd.Start(); err != nil {
        return nil, fmt.Errorf("fusermount: %v", err)
    }
    helperErrCh := make(chan error, 1)
    wg.Add(2)
    go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
    go lineLogger(&wg, "mount helper error", handleFusermountStderr(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 !isBoringFusermountError(err) {
                log.Printf("mount helper failed: %v", err)
            }
            // and now return what we grabbed from stderr as the real
            // error
            return nil, helperErr
        default:
            // nope, fall back to generic message
        }

        return nil, fmt.Errorf("fusermount: %v", err)
    }

    c, err := net.FileConn(readFile)
    if err != nil {
        return nil, fmt.Errorf("FileConn from fusermount socket: %v", err)
    }
    defer c.Close()

    uc, ok := c.(*net.UnixConn)
    if !ok {
        return nil, fmt.Errorf("unexpected FileConn type; expected UnixConn, got %T", c)
    }

    buf := make([]byte, 32) // expect 1 byte
    oob := make([]byte, 32) // expect 24 bytes
    _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
    scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
    if err != nil {
        return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
    }
    if len(scms) != 1 {
        return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
    }
    scm := scms[0]
    gotFds, err := syscall.ParseUnixRights(&scm)
    if err != nil {
        return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
    }
    if len(gotFds) != 1 {
        return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
    }
    f := os.NewFile(uintptr(gotFds[0]), "/dev/fuse")
    return f, nil
}