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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server is an interface for any type that knows how to serve ops read from a
|
// 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
|
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.
|
// Environment variable used to pass the "called by library" flag.
|
||||||
LibVar string
|
LibVar string
|
||||||
|
|
||||||
|
// Open device manually (false) or receive the FD through a UNIX socket,
|
||||||
|
// like with fusermount (true)
|
||||||
|
UseCommFD bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -51,6 +55,7 @@ var (
|
||||||
Mount: "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse",
|
Mount: "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse",
|
||||||
DaemonVar: "_FUSE_DAEMON_PATH",
|
DaemonVar: "_FUSE_DAEMON_PATH",
|
||||||
LibVar: "_FUSE_CALL_BY_LIB",
|
LibVar: "_FUSE_CALL_BY_LIB",
|
||||||
|
UseCommFD: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// v3
|
// 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(
|
func callMount(
|
||||||
bin string,
|
bin string,
|
||||||
daemonVar string,
|
daemonVar string,
|
||||||
|
@ -115,39 +150,21 @@ func callMount(
|
||||||
dev *os.File,
|
dev *os.File,
|
||||||
ready chan<- error) error {
|
ready chan<- error) error {
|
||||||
|
|
||||||
// The mount helper doesn't understand any escaping.
|
argv, env, err := convertMountArgs(daemonVar, libVar, cfg)
|
||||||
for k, v := range cfg.toMap() {
|
if err != nil {
|
||||||
if strings.Contains(k, ",") || strings.Contains(v, ",") {
|
return err
|
||||||
return fmt.Errorf(
|
|
||||||
"mount options cannot contain commas on darwin: %q=%q",
|
|
||||||
k,
|
|
||||||
v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the mount helper, passing in the device file and saving output into a
|
// Call the mount helper, passing in the device file and saving output into a
|
||||||
// buffer.
|
// buffer.
|
||||||
cmd := exec.Command(
|
argv = append(argv,
|
||||||
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),
|
|
||||||
// refers to fd passed in cmd.ExtraFiles
|
// refers to fd passed in cmd.ExtraFiles
|
||||||
"3",
|
"3",
|
||||||
dir,
|
dir,
|
||||||
)
|
)
|
||||||
|
cmd := exec.Command(bin, argv...)
|
||||||
cmd.ExtraFiles = []*os.File{dev}
|
cmd.ExtraFiles = []*os.File{dev}
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = env
|
||||||
cmd.Env = append(cmd.Env, libVar+"=")
|
|
||||||
|
|
||||||
daemon := os.Args[0]
|
|
||||||
if daemonVar != "" {
|
|
||||||
cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
cmd.Stdout = &buf
|
cmd.Stdout = &buf
|
||||||
|
@ -174,6 +191,23 @@ func callMount(
|
||||||
return nil
|
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
|
// 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
|
// 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
|
// error is written to the supplied channel. The file system may need to
|
||||||
|
@ -189,6 +223,16 @@ func mount(
|
||||||
continue
|
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.
|
// Open the device.
|
||||||
dev, err = openOSXFUSEDev(loc.DevicePrefix)
|
dev, err = openOSXFUSEDev(loc.DevicePrefix)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package fuse
|
package fuse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -23,92 +21,6 @@ func findFusermount() (string, error) {
|
||||||
return path, nil
|
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 {
|
func enableFunc(flag uintptr) func(uintptr) uintptr {
|
||||||
return func(v uintptr) uintptr {
|
return func(v uintptr) uintptr {
|
||||||
return v | flag
|
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.
|
// have the CAP_SYS_ADMIN capability.
|
||||||
dev, err := directmount(dir, cfg)
|
dev, err := directmount(dir, cfg)
|
||||||
if err == errFallback {
|
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
|
return dev, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue