mount_darwin.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package fuse
  2. import (
  3. "errors"
  4. "fmt"
  5. "log"
  6. "os"
  7. "os/exec"
  8. "path"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "syscall"
  13. )
  14. var (
  15. errNoAvail = errors.New("no available fuse devices")
  16. errNotLoaded = errors.New("osxfuse is not loaded")
  17. )
  18. func loadOSXFUSE(bin string) error {
  19. cmd := exec.Command(bin)
  20. cmd.Dir = "/"
  21. cmd.Stdout = os.Stdout
  22. cmd.Stderr = os.Stderr
  23. err := cmd.Run()
  24. return err
  25. }
  26. func openOSXFUSEDev(devPrefix string) (*os.File, error) {
  27. var f *os.File
  28. var err error
  29. for i := uint64(0); ; i++ {
  30. path := devPrefix + strconv.FormatUint(i, 10)
  31. f, err = os.OpenFile(path, os.O_RDWR, 0000)
  32. if os.IsNotExist(err) {
  33. if i == 0 {
  34. // not even the first device was found -> fuse is not loaded
  35. return nil, errNotLoaded
  36. }
  37. // we've run out of kernel-provided devices
  38. return nil, errNoAvail
  39. }
  40. if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY {
  41. // try the next one
  42. continue
  43. }
  44. if err != nil {
  45. return nil, err
  46. }
  47. return f, nil
  48. }
  49. }
  50. func handleMountOSXFUSE(helperName string, errCh chan<- error) func(line string) (ignore bool) {
  51. var noMountpointPrefix = helperName + `: `
  52. const noMountpointSuffix = `: No such file or directory`
  53. return func(line string) (ignore bool) {
  54. if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
  55. // re-extract it from the error message in case some layer
  56. // changed the path
  57. mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
  58. err := &MountpointDoesNotExistError{
  59. Path: mountpoint,
  60. }
  61. select {
  62. case errCh <- err:
  63. return true
  64. default:
  65. // not the first error; fall back to logging it
  66. return false
  67. }
  68. }
  69. return false
  70. }
  71. }
  72. // isBoringMountOSXFUSEError returns whether the Wait error is
  73. // uninteresting; exit status 64 is.
  74. func isBoringMountOSXFUSEError(err error) bool {
  75. if err, ok := err.(*exec.ExitError); ok && err.Exited() {
  76. if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 64 {
  77. return true
  78. }
  79. }
  80. return false
  81. }
  82. func callMount(bin string, daemonVar string, dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
  83. for k, v := range conf.options {
  84. if strings.Contains(k, ",") || strings.Contains(v, ",") {
  85. // Silly limitation but the mount helper does not
  86. // understand any escaping. See TestMountOptionCommaError.
  87. return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v)
  88. }
  89. }
  90. cmd := exec.Command(
  91. bin,
  92. "-o", conf.getOptions(),
  93. // Tell osxfuse-kext how large our buffer is. It must split
  94. // writes larger than this into multiple writes.
  95. //
  96. // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
  97. // this instead.
  98. "-o", "iosize="+strconv.FormatUint(maxWrite, 10),
  99. // refers to fd passed in cmd.ExtraFiles
  100. "3",
  101. dir,
  102. )
  103. cmd.ExtraFiles = []*os.File{f}
  104. cmd.Env = os.Environ()
  105. // OSXFUSE <3.3.0
  106. cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
  107. // OSXFUSE >=3.3.0
  108. cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=")
  109. daemon := os.Args[0]
  110. if daemonVar != "" {
  111. cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
  112. }
  113. stdout, err := cmd.StdoutPipe()
  114. if err != nil {
  115. return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
  116. }
  117. stderr, err := cmd.StderrPipe()
  118. if err != nil {
  119. return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
  120. }
  121. if err := cmd.Start(); err != nil {
  122. return fmt.Errorf("mount_osxfusefs: %v", err)
  123. }
  124. helperErrCh := make(chan error, 1)
  125. go func() {
  126. var wg sync.WaitGroup
  127. wg.Add(2)
  128. go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
  129. helperName := path.Base(bin)
  130. go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, helperErrCh), stderr)
  131. wg.Wait()
  132. if err := cmd.Wait(); err != nil {
  133. // see if we have a better error to report
  134. select {
  135. case helperErr := <-helperErrCh:
  136. // log the Wait error if it's not what we expected
  137. if !isBoringMountOSXFUSEError(err) {
  138. log.Printf("mount helper failed: %v", err)
  139. }
  140. // and now return what we grabbed from stderr as the real
  141. // error
  142. *errp = helperErr
  143. close(ready)
  144. return
  145. default:
  146. // nope, fall back to generic message
  147. }
  148. *errp = fmt.Errorf("mount_osxfusefs: %v", err)
  149. close(ready)
  150. return
  151. }
  152. *errp = nil
  153. close(ready)
  154. }()
  155. return nil
  156. }
  157. func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
  158. locations := conf.osxfuseLocations
  159. if locations == nil {
  160. locations = []OSXFUSEPaths{
  161. OSXFUSELocationV3,
  162. OSXFUSELocationV2,
  163. }
  164. }
  165. for _, loc := range locations {
  166. if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
  167. // try the other locations
  168. continue
  169. }
  170. f, err := openOSXFUSEDev(loc.DevicePrefix)
  171. if err == errNotLoaded {
  172. err = loadOSXFUSE(loc.Load)
  173. if err != nil {
  174. return nil, err
  175. }
  176. // try again
  177. f, err = openOSXFUSEDev(loc.DevicePrefix)
  178. }
  179. if err != nil {
  180. return nil, err
  181. }
  182. err = callMount(loc.Mount, loc.DaemonVar, dir, conf, f, ready, errp)
  183. if err != nil {
  184. f.Close()
  185. return nil, err
  186. }
  187. return f, nil
  188. }
  189. return nil, ErrOSXFUSENotFound
  190. }