Use newer mounting method (similar to fusermount) with macfuse 4.x
macfuse 4.x turns out to be incompatible with the old mounting method where you open the device by yourself and only supports the newer method where you receive a file descriptor from `mount_macfuse` through a unix socket.geesefs-0-30-9
parent
575b70f3fd
commit
ec521aa7b7
82
mount.go
82
mount.go
|
@ -17,7 +17,10 @@ package fuse
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Server is an interface for any type that knows how to serve ops read from a
|
||||
|
@ -93,3 +96,82 @@ func Mount(
|
|||
|
||||
return mfs, nil
|
||||
}
|
||||
|
||||
func fusermount(binary string, argv []string, additionalEnv []string, wait bool) (*os.File, error) {
|
||||
// Create a socket pair.
|
||||
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Socketpair: %v", err)
|
||||
}
|
||||
|
||||
// Wrap the sockets into os.File objects that we will pass off to fusermount.
|
||||
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
|
||||
defer writeFile.Close()
|
||||
|
||||
readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
|
||||
defer readFile.Close()
|
||||
|
||||
// Start fusermount/mount_macfuse/mount_osxfuse.
|
||||
cmd := exec.Command(binary, argv...)
|
||||
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
|
||||
cmd.Env = append(cmd.Env, additionalEnv...)
|
||||
cmd.ExtraFiles = []*os.File{writeFile}
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// Run the command.
|
||||
if wait {
|
||||
err = cmd.Run()
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("running %v: %v", binary, err)
|
||||
}
|
||||
|
||||
// Wrap the socket file in a connection.
|
||||
c, err := net.FileConn(readFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FileConn: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// We expect to have a Unix domain socket.
|
||||
uc, ok := c.(*net.UnixConn)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Expected UnixConn, got %T", c)
|
||||
}
|
||||
|
||||
// Read a message.
|
||||
buf := make([]byte, 32) // expect 1 byte
|
||||
oob := make([]byte, 32) // expect 24 bytes
|
||||
_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ReadMsgUnix: %v", err)
|
||||
}
|
||||
|
||||
// Parse the message.
|
||||
scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
|
||||
}
|
||||
|
||||
// We expect one message.
|
||||
if len(scms) != 1 {
|
||||
return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
|
||||
}
|
||||
|
||||
scm := scms[0]
|
||||
|
||||
// Pull out the FD returned by fusermount
|
||||
gotFds, err := syscall.ParseUnixRights(&scm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
|
||||
}
|
||||
|
||||
if len(gotFds) != 1 {
|
||||
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
|
||||
}
|
||||
|
||||
// Turn the FD into an os.File.
|
||||
return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@ type osxfuseInstallation struct {
|
|||
|
||||
// Environment variable used to pass the "called by library" flag.
|
||||
LibVar string
|
||||
|
||||
// Open device manually (false) or receive the FD through a UNIX socket,
|
||||
// like with fusermount (true)
|
||||
UseCommFD bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -51,6 +55,7 @@ var (
|
|||
Mount: "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse",
|
||||
DaemonVar: "_FUSE_DAEMON_PATH",
|
||||
LibVar: "_FUSE_CALL_BY_LIB",
|
||||
UseCommFD: true,
|
||||
},
|
||||
|
||||
// v3
|
||||
|
@ -106,6 +111,36 @@ func openOSXFUSEDev(devPrefix string) (dev *os.File, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func convertMountArgs(daemonVar string, libVar string,
|
||||
cfg *MountConfig) ([]string, []string, error) {
|
||||
|
||||
// The mount helper doesn't understand any escaping.
|
||||
for k, v := range cfg.toMap() {
|
||||
if strings.Contains(k, ",") || strings.Contains(v, ",") {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"mount options cannot contain commas on darwin: %q=%q",
|
||||
k,
|
||||
v)
|
||||
}
|
||||
}
|
||||
|
||||
env := []string{ libVar+"=" }
|
||||
if daemonVar != "" {
|
||||
env = append(env, daemonVar+"="+os.Args[0])
|
||||
}
|
||||
argv := []string{
|
||||
"-o", cfg.toOptionsString(),
|
||||
// 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(buffer.MaxWriteSize, 10),
|
||||
}
|
||||
|
||||
return argv, env, nil
|
||||
}
|
||||
|
||||
func callMount(
|
||||
bin string,
|
||||
daemonVar string,
|
||||
|
@ -115,39 +150,21 @@ func callMount(
|
|||
dev *os.File,
|
||||
ready chan<- error) error {
|
||||
|
||||
// The mount helper doesn't understand any escaping.
|
||||
for k, v := range cfg.toMap() {
|
||||
if strings.Contains(k, ",") || strings.Contains(v, ",") {
|
||||
return fmt.Errorf(
|
||||
"mount options cannot contain commas on darwin: %q=%q",
|
||||
k,
|
||||
v)
|
||||
}
|
||||
argv, env, err := convertMountArgs(daemonVar, libVar, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call the mount helper, passing in the device file and saving output into a
|
||||
// buffer.
|
||||
cmd := exec.Command(
|
||||
bin,
|
||||
"-o", cfg.toOptionsString(),
|
||||
// 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(buffer.MaxWriteSize, 10),
|
||||
argv = append(argv,
|
||||
// refers to fd passed in cmd.ExtraFiles
|
||||
"3",
|
||||
dir,
|
||||
)
|
||||
cmd := exec.Command(bin, argv...)
|
||||
cmd.ExtraFiles = []*os.File{dev}
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, libVar+"=")
|
||||
|
||||
daemon := os.Args[0]
|
||||
if daemonVar != "" {
|
||||
cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
|
||||
}
|
||||
cmd.Env = env
|
||||
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
|
@ -174,6 +191,23 @@ func callMount(
|
|||
return nil
|
||||
}
|
||||
|
||||
func callMountCommFD(
|
||||
bin string,
|
||||
daemonVar string,
|
||||
libVar string,
|
||||
dir string,
|
||||
cfg *MountConfig) (*os.File, error) {
|
||||
|
||||
argv, env, err := convertMountArgs(daemonVar, libVar, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env = append(env, "_FUSE_COMMVERS=2")
|
||||
argv = append(argv, dir)
|
||||
|
||||
return fusermount(bin, argv, env, false)
|
||||
}
|
||||
|
||||
// Begin the process of mounting at the given directory, returning a connection
|
||||
// to the kernel. Mounting continues in the background, and is complete when an
|
||||
// error is written to the supplied channel. The file system may need to
|
||||
|
@ -189,6 +223,16 @@ func mount(
|
|||
continue
|
||||
}
|
||||
|
||||
if loc.UseCommFD {
|
||||
// Call the mount binary with the device.
|
||||
ready <- nil
|
||||
dev, err = callMountCommFD(loc.Mount, loc.DaemonVar, loc.LibVar, dir, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("callMount: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Open the device.
|
||||
dev, err = openOSXFUSEDev(loc.DevicePrefix)
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package fuse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
@ -23,92 +21,6 @@ func findFusermount() (string, error) {
|
|||
return path, nil
|
||||
}
|
||||
|
||||
func fusermount(dir string, cfg *MountConfig) (*os.File, error) {
|
||||
// Create a socket pair.
|
||||
fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Socketpair: %v", err)
|
||||
}
|
||||
|
||||
// Wrap the sockets into os.File objects that we will pass off to fusermount.
|
||||
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
|
||||
defer writeFile.Close()
|
||||
|
||||
readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
|
||||
defer readFile.Close()
|
||||
|
||||
// Start fusermount, passing it a buffer in which to write stderr.
|
||||
var stderr bytes.Buffer
|
||||
|
||||
fusermount, err := findFusermount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := exec.Command(
|
||||
fusermount,
|
||||
"-o", cfg.toOptionsString(),
|
||||
"--",
|
||||
dir,
|
||||
)
|
||||
|
||||
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
|
||||
cmd.ExtraFiles = []*os.File{writeFile}
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
// Run the command.
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes())
|
||||
}
|
||||
|
||||
// Wrap the socket file in a connection.
|
||||
c, err := net.FileConn(readFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FileConn: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// We expect to have a Unix domain socket.
|
||||
uc, ok := c.(*net.UnixConn)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Expected UnixConn, got %T", c)
|
||||
}
|
||||
|
||||
// Read a message.
|
||||
buf := make([]byte, 32) // expect 1 byte
|
||||
oob := make([]byte, 32) // expect 24 bytes
|
||||
_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ReadMsgUnix: %v", err)
|
||||
}
|
||||
|
||||
// Parse the message.
|
||||
scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
|
||||
}
|
||||
|
||||
// We expect one message.
|
||||
if len(scms) != 1 {
|
||||
return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
|
||||
}
|
||||
|
||||
scm := scms[0]
|
||||
|
||||
// Pull out the FD returned by fusermount
|
||||
gotFds, err := syscall.ParseUnixRights(&scm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
|
||||
}
|
||||
|
||||
if len(gotFds) != 1 {
|
||||
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
|
||||
}
|
||||
|
||||
// Turn the FD into an os.File.
|
||||
return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil
|
||||
}
|
||||
|
||||
func enableFunc(flag uintptr) func(uintptr) uintptr {
|
||||
return func(v uintptr) uintptr {
|
||||
return v | flag
|
||||
|
@ -198,7 +110,16 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
|
|||
// have the CAP_SYS_ADMIN capability.
|
||||
dev, err := directmount(dir, cfg)
|
||||
if err == errFallback {
|
||||
return fusermount(dir, cfg)
|
||||
fusermountPath, err := findFusermount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argv := []string{
|
||||
"-o", cfg.toOptionsString(),
|
||||
"--",
|
||||
dir,
|
||||
}
|
||||
return fusermount(fusermountPath, argv, []string{}, true)
|
||||
}
|
||||
return dev, err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue