Use newer mounting method (similar to fusermount) with macfuse 4.x (#106)
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. Co-authored-by: Vitaliy Filippov <vitalif@yourcmc.ru>zerocopy-examples
parent
95fc8d1181
commit
c75d3f26fc
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