123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- 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
- }
|