aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/bazil.org/fuse/mount_linux.go
blob: 197d1044e926daa8cd48b61992fcceeaac60e8e7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
}