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