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  }