mount_darwin.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. package fuse
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "os/exec"
  8. "strconv"
  9. "strings"
  10. "syscall"
  11. "github.com/jacobsa/fuse/internal/buffer"
  12. )
  13. var errNoAvail = errors.New("no available fuse devices")
  14. var errNotLoaded = errors.New("osxfuse is not loaded")
  15. // errOSXFUSENotFound is returned from Mount when the OSXFUSE installation is
  16. // not detected. Make sure OSXFUSE is installed.
  17. var errOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
  18. // osxfuseInstallation describes the paths used by an installed OSXFUSE
  19. // version.
  20. type osxfuseInstallation struct {
  21. // Prefix for the device file. At mount time, an incrementing number is
  22. // suffixed until a free FUSE device is found.
  23. DevicePrefix string
  24. // Path of the load helper, used to load the kernel extension if no device
  25. // files are found.
  26. Load string
  27. // Path of the mount helper, used for the actual mount operation.
  28. Mount string
  29. // Environment variable used to pass the path to the executable calling the
  30. // mount helper.
  31. DaemonVar string
  32. }
  33. var (
  34. osxfuseInstallations = []osxfuseInstallation{
  35. // v3
  36. {
  37. DevicePrefix: "/dev/osxfuse",
  38. Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
  39. Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
  40. DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
  41. },
  42. // v2
  43. {
  44. DevicePrefix: "/dev/osxfuse",
  45. Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
  46. Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
  47. DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
  48. },
  49. }
  50. )
  51. func loadOSXFUSE(bin string) error {
  52. cmd := exec.Command(bin)
  53. cmd.Dir = "/"
  54. cmd.Stdout = os.Stdout
  55. cmd.Stderr = os.Stderr
  56. err := cmd.Run()
  57. return err
  58. }
  59. func openOSXFUSEDev(devPrefix string) (dev *os.File, err error) {
  60. // Try each device name.
  61. for i := uint64(0); ; i++ {
  62. path := devPrefix + strconv.FormatUint(i, 10)
  63. dev, err = os.OpenFile(path, os.O_RDWR, 0000)
  64. if os.IsNotExist(err) {
  65. if i == 0 {
  66. // Not even the first device was found. Fuse must not be loaded.
  67. err = errNotLoaded
  68. return
  69. }
  70. // Otherwise we've run out of kernel-provided devices
  71. err = errNoAvail
  72. return
  73. }
  74. if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY {
  75. // This device is in use; try the next one.
  76. continue
  77. }
  78. return
  79. }
  80. }
  81. func callMount(
  82. bin string,
  83. daemonVar string,
  84. dir string,
  85. cfg *MountConfig,
  86. dev *os.File,
  87. ready chan<- error) (err error) {
  88. // The mount helper doesn't understand any escaping.
  89. for k, v := range cfg.toMap() {
  90. if strings.Contains(k, ",") || strings.Contains(v, ",") {
  91. return fmt.Errorf(
  92. "mount options cannot contain commas on darwin: %q=%q",
  93. k,
  94. v)
  95. }
  96. }
  97. // Call the mount helper, passing in the device file and saving output into a
  98. // buffer.
  99. cmd := exec.Command(
  100. bin,
  101. "-o", cfg.toOptionsString(),
  102. // Tell osxfuse-kext how large our buffer is. It must split
  103. // writes larger than this into multiple writes.
  104. //
  105. // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
  106. // this instead.
  107. "-o", "iosize="+strconv.FormatUint(buffer.MaxWriteSize, 10),
  108. // refers to fd passed in cmd.ExtraFiles
  109. "3",
  110. dir,
  111. )
  112. cmd.ExtraFiles = []*os.File{dev}
  113. cmd.Env = os.Environ()
  114. // OSXFUSE <3.3.0
  115. cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
  116. // OSXFUSE >=3.3.0
  117. cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=")
  118. daemon := os.Args[0]
  119. if daemonVar != "" {
  120. cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
  121. }
  122. var buf bytes.Buffer
  123. cmd.Stdout = &buf
  124. cmd.Stderr = &buf
  125. err = cmd.Start()
  126. if err != nil {
  127. return
  128. }
  129. // In the background, wait for the command to complete.
  130. go func() {
  131. err := cmd.Wait()
  132. if err != nil {
  133. if buf.Len() > 0 {
  134. output := buf.Bytes()
  135. output = bytes.TrimRight(output, "\n")
  136. err = fmt.Errorf("%v: %s", err, output)
  137. }
  138. }
  139. ready <- err
  140. }()
  141. return
  142. }
  143. // Begin the process of mounting at the given directory, returning a connection
  144. // to the kernel. Mounting continues in the background, and is complete when an
  145. // error is written to the supplied channel. The file system may need to
  146. // service the connection in order for mounting to complete.
  147. func mount(
  148. dir string,
  149. cfg *MountConfig,
  150. ready chan<- error) (dev *os.File, err error) {
  151. // Find the version of osxfuse installed on this machine.
  152. for _, loc := range osxfuseInstallations {
  153. if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
  154. // try the other locations
  155. continue
  156. }
  157. // Open the device.
  158. dev, err = openOSXFUSEDev(loc.DevicePrefix)
  159. // Special case: we may need to explicitly load osxfuse. Load it, then
  160. // try again.
  161. if err == errNotLoaded {
  162. err = loadOSXFUSE(loc.Load)
  163. if err != nil {
  164. err = fmt.Errorf("loadOSXFUSE: %v", err)
  165. return
  166. }
  167. dev, err = openOSXFUSEDev(loc.DevicePrefix)
  168. }
  169. // Propagate errors.
  170. if err != nil {
  171. err = fmt.Errorf("openOSXFUSEDev: %v", err)
  172. return
  173. }
  174. // Call the mount binary with the device.
  175. err = callMount(loc.Mount, loc.DaemonVar, dir, cfg, dev, ready)
  176. if err != nil {
  177. dev.Close()
  178. err = fmt.Errorf("callMount: %v", err)
  179. return
  180. }
  181. return
  182. }
  183. err = errOSXFUSENotFound
  184. return
  185. }