Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement FUSE-T support on macOS #453

Closed
wants to merge 11 commits into from
109 changes: 101 additions & 8 deletions fuse/mount_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,112 @@ import (
"unsafe"
)

const FUSET_SRV_PATH = "/usr/local/bin/go-nfsv4"
macos-fuse-t marked this conversation as resolved.
Show resolved Hide resolved

var osxFuse bool

func unixgramSocketpair() (l, r *os.File, err error) {
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, nil, os.NewSyscallError("socketpair",
err.(syscall.Errno))
}
l = os.NewFile(uintptr(fd[0]), "socketpair-half1")
r = os.NewFile(uintptr(fd[1]), "socketpair-half2")
l = os.NewFile(uintptr(fd[0]), fmt.Sprintf("socketpair-half%d", fd[0]))
r = os.NewFile(uintptr(fd[1]), fmt.Sprintf("socketpair-half%d", fd[1]))
return
}

func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
if fuset_bin, err := fusetBinary(); err == nil {
osxFuse = false
return mount_fuset(fuset_bin, mountPoint, opts, ready)
} else if osxfuse_bin, err := fusermountBinary(); err == nil {
osxFuse = true
return mount_osxfuse(osxfuse_bin, mountPoint, opts, ready)
}
return -1, fmt.Errorf("not FUSE-T nor osxFuse found")
}

// Declare these as globals to prevent them from being garbage collected,
// as we utilize the underlying file descriptors rather than the objects.
var local, local_mon, remote, remote_mon *os.File
macos-fuse-t marked this conversation as resolved.
Show resolved Hide resolved

// Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute.
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
local, remote, err := unixgramSocketpair()
func mount_fuset(bin string, mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
local, remote, err = unixgramSocketpair()
if err != nil {
return
}

defer local.Close()
defer remote.Close()

bin, err := fusermountBinary()
local_mon, remote_mon, err = unixgramSocketpair()
if err != nil {
return 0, err
return
}
defer remote_mon.Close()

args := []string{}
if opts.Debug {
args = append(args, "-d")
}
if opts.FsName != "" {
args = append(args, "--volname")
args = append(args, opts.FsName)
}
for _, opts := range opts.optionsStrings() {
if opts == "ro" {
args = append(args, "-r")
}
}

args = append(args, fmt.Sprintf("--rwsize=%d", opts.MaxWrite))
args = append(args, mountPoint)
cmd := exec.Command(bin, args...)
cmd.ExtraFiles = []*os.File{remote, remote_mon} // fd would be (index + 3)

envs := []string{}
envs = append(envs, "_FUSE_COMMFD=3")
envs = append(envs, "_FUSE_MONFD=4")
envs = append(envs, "_FUSE_COMMVERS=2")
cmd.Env = append(os.Environ(), envs...)

syscall.CloseOnExec(int(local.Fd()))
syscall.CloseOnExec(int(local_mon.Fd()))

if err = cmd.Start(); err != nil {
return
}
cmd.Process.Release()
fd = int(local.Fd())
go func() {
if _, err = local_mon.Write([]byte("mount")); err != nil {
err = fmt.Errorf("fuse-t failed: %v", err)
} else {
reply := make([]byte, 4)
if _, err = local_mon.Read(reply); err != nil {
fmt.Printf("mount read %v\n", err)
err = fmt.Errorf("fuse-t failed: %v", err)
}
}

ready <- err
close(ready)
}()

return fd, err
}

// Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute.
func mount_osxfuse(bin string, mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
local, remote, err := unixgramSocketpair()
if err != nil {
return
}

defer local.Close()
defer remote.Close()

cmd := exec.Command(bin,
"-o", strings.Join(opts.optionsStrings(), ","),
Expand Down Expand Up @@ -131,3 +211,16 @@ func fusermountBinary() (string, error) {

return "", fmt.Errorf("no FUSE mount utility found")
}

func fusetBinary() (string, error) {
srv_path := os.Getenv("FUSE_NFSSRV_PATH")
if srv_path == "" {
srv_path = FUSET_SRV_PATH
}

if _, err := os.Stat(srv_path); err == nil {
return srv_path, nil
}

return "", fmt.Errorf("FUSE-T not found")
}
11 changes: 11 additions & 0 deletions fuse/nodefs/memnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ func (n *memNode) GetAttr(fi *fuse.Attr, file File, context *fuse.Context) (code
return fuse.OK
}

func (n *memNode) Lookup(fi *fuse.Attr, name string, context *fuse.Context) (node *Inode, code fuse.Status) {
n.mu.Lock()
defer n.mu.Unlock()

*fi = n.info
if n.Inode().GetChild(name) == nil {
return nil, fuse.ENOENT
}
return n.Inode().GetChild(name), fuse.OK
}

func (n *memNode) Truncate(file File, size uint64, context *fuse.Context) (code fuse.Status) {
if file != nil {
code = file.Truncate(size)
Expand Down
10 changes: 4 additions & 6 deletions fuse/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,7 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
destIface := ms.readPool.Get()
dest := destIface.([]byte)

var n int
err := handleEINTR(func() error {
var err error
n, err = syscall.Read(ms.mountFd, dest)
return err
})
n, err := ms.systemRead(dest)
if err != nil {
code = ToStatus(err)
ms.reqPool.Put(reqIface)
Expand Down Expand Up @@ -527,7 +522,10 @@ func (ms *Server) handleRequest(req *request) Status {
req.handler.Func(ms, req)
}

ms.writeMu.Lock()
errNo := ms.write(req)
ms.writeMu.Unlock()

if errNo != 0 {
// Unless debugging is enabled, ignore ENOENT for INTERRUPT responses
// which indicates that the referred request is no longer known by the
Expand Down
53 changes: 53 additions & 0 deletions fuse/server_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package fuse

import (
"sync"
"syscall"
"unsafe"
)

func (ms *Server) systemWrite(req *request, header []byte) Status {
Expand All @@ -30,3 +32,54 @@ func (ms *Server) systemWrite(req *request, header []byte) Status {
}
return ToStatus(err)
}

func readAll(fd int, dest []byte) (int, error) {
offset := 0

for offset < len(dest) {
// read the remaining buffer
err := handleEINTR(func() error {
n, err := syscall.Read(fd, dest[offset:])
if n == 0 && err == nil {
// remote fd closed
return syscall.EIO
}
offset += n
return err
})
if err != nil {
return offset, err
}
}
return offset, nil
}

// for a stream connection we need to have a single reader
var readLock sync.Mutex
macos-fuse-t marked this conversation as resolved.
Show resolved Hide resolved

func (ms *Server) systemRead(dest []byte) (int, error) {
var n int
if osxFuse {
err := handleEINTR(func() error {
var err error
n, err = syscall.Read(ms.mountFd, dest)
return err
})
return n, err
}

readLock.Lock()
defer readLock.Unlock()

// read request length
if _, err := readAll(ms.mountFd, dest[0:4]); err != nil {
return 0, err
}

l := *(*uint32)(unsafe.Pointer(&dest[0]))
// read remaining request
if _, err := readAll(ms.mountFd, dest[4:l]); err != nil {
return n, err
}
return int(l), nil
}
10 changes: 10 additions & 0 deletions fuse/server_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,13 @@ func (ms *Server) systemWrite(req *request, header []byte) Status {
}
return ToStatus(err)
}

func (ms *Server) systemRead(dest []byte) (int, error) {
var n int
err := handleEINTR(func() error {
var err error
n, err = syscall.Read(ms.mountFd, dest)
return err
})
return n, err
}
Loading