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 }