Commit ebe08a81 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Rewrite mount_darwin.go, obviating CGO.

The strategy was inspired by the mount code for Darwin in
bazil.org/fuse.

Unfortunately, OSX must have the event loop started before mounting
can be completed. This means that WaitMount() must be maintained for
use on OSX.

Change-Id: Ie53425f306b4575b3e94e45407bab05017f5ce14
parent 35b967f3
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TODO: Rewrite using package syscall not cgo
package fuse package fuse
/* import (
"bytes"
// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c, "fmt"
// which carries this notice: "os"
// "os/exec"
// The files in this directory are subject to the following license. "path/filepath"
// "syscall"
// The author of this software is Russ Cox. )
//
// Copyright (c) 2006 Russ Cox
//
// Permission to use, copy, modify, and distribute this software for any
// purpose without fee is hereby granted, provided that this entire notice
// is included in all copies of any software which is or includes a copy
// or modification of this software and in all copies of the supporting
// documentation for such software.
//
// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
// FITNESS FOR ANY PARTICULAR PURPOSE.
#include <stdlib.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#define nil ((void*)0)
static int
mountfuse(char *mtpt, char **err)
{
int i, pid, fd, r;
char buf[200];
struct vfsconf vfs;
char *f;
if(getvfsbyname("osxfusefs", &vfs) < 0){ func openFUSEDevice() (*os.File, error) {
if(access(f="/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs", 0) < 0){ fs, err := filepath.Glob("/dev/osxfuse*")
*err = strdup("cannot find load_fusefs"); if err != nil {
return -1; return nil, err
}
if((r=system(f)) < 0){
snprintf(buf, sizeof buf, "%s: %s", f, strerror(errno));
*err = strdup(buf);
return -1;
}
if(r != 0){
snprintf(buf, sizeof buf, "load_fusefs failed: exit %d", r);
*err = strdup(buf);
return -1;
}
if(getvfsbyname("osxfusefs", &vfs) < 0){
snprintf(buf, sizeof buf, "getvfsbyname osxfusefs: %s", strerror(errno));
*err = strdup(buf);
return -1;
}
} }
if len(fs) == 0 {
// Look for available FUSE device. // TODO(hanwen): run the load_osxfuse command.
for(i=0;; i++){ return nil, fmt.Errorf("no FUSE devices found")
snprintf(buf, sizeof buf, "/dev/osxfuse%d", i); }
if(access(buf, 0) < 0){ for _, fn := range fs {
*err = strdup("no available fuse devices"); f, err := os.OpenFile(fn, os.O_RDWR, 0)
return -1; if err != nil {
continue
} }
if((fd = open(buf, O_RDWR)) >= 0) return f, nil
break;
} }
pid = fork(); return nil, fmt.Errorf("all FUSE devices busy")
if(pid < 0)
return -1;
if(pid == 0){
snprintf(buf, sizeof buf, "%d", fd);
setenv("MOUNT_FUSEFS_CALL_BY_LIB", "", 1);
// Different versions of MacFUSE put the
// mount_fusefs binary in different places.
// Try all.
// Leopard location
setenv("MOUNT_FUSEFS_DAEMON_PATH", "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs", 1);
execl("/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs", "mount_osxfusefs", "-o", "iosize=4096", buf, mtpt, nil);
fprintf(stderr, "exec mount_osxfusefs: %s\n", strerror(errno));
_exit(1);
}
return fd;
} }
*/ const bin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
import "C"
import (
"fmt"
"log"
"os/exec"
"syscall"
"unsafe"
)
func mount(dir string, options string) (int, error) { func mount(mountPoint, options string, ready chan<- error) (fd int, err error) {
errp := (**C.char)(C.malloc(16)) f, err := openFUSEDevice()
*errp = nil if err != nil {
defer C.free(unsafe.Pointer(errp)) return 0, err
cdir := C.CString(dir)
defer C.free(unsafe.Pointer(cdir))
fd := C.mountfuse(cdir, errp)
if *errp != nil {
return -1, mountError(C.GoString(*errp))
} }
return int(fd), nil
}
type mountError string cmd := exec.Command(bin, "-o", options, "-o", fmt.Sprintf("iosize=%d", MAX_KERNEL_WRITE), "3", mountPoint)
cmd.ExtraFiles = []*os.File{f}
cmd.Env = append(os.Environ(), "MOUNT_FUSEFS_CALL_BY_LIB=", "MOUNT_OSXFUSE_CALL_BY_LIB=",
"MOUNT_OSXFUSE_DAEMON_PATH="+os.Args[0],
"MOUNT_FUSEFS_DAEMON_PATH="+os.Args[0])
func (m mountError) Error() string { var out, errOut bytes.Buffer
return string(m) cmd.Stdout = &out
} cmd.Stderr = &errOut
func unmount(mountPoint string) error { if err := cmd.Start(); err != nil {
if err := syscall.Unmount(mountPoint, 0); err != nil { f.Close()
return fmt.Errorf("umount(%q): %v", mountPoint, err) return 0, err
} }
return nil go func() {
} err := cmd.Wait()
if err != nil {
err = fmt.Errorf("mount_osxfusefs failed: %v. Stderr: %s, Stdout: %s", err, errOut.String(), out.String())
}
var umountBinary string ready <- err
close(ready)
}()
return int(f.Fd()), nil
}
func init() { func unmount(dir string) error {
var err error return syscall.Unmount(dir, 0)
umountBinary, err = exec.LookPath("umount")
if err != nil {
log.Fatalf("Could not find umount binary: %v", err)
}
} }
...@@ -24,7 +24,7 @@ func unixgramSocketpair() (l, r *os.File, err error) { ...@@ -24,7 +24,7 @@ func unixgramSocketpair() (l, r *os.File, err error) {
// Create a FUSE FS on the specified mount point. The returned // Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute. // mount point is always absolute.
func mount(mountPoint string, options string) (fd int, err error) { func mount(mountPoint string, options string, ready chan<- error) (fd int, err error) {
local, remote, err := unixgramSocketpair() local, remote, err := unixgramSocketpair()
if err != nil { if err != nil {
return return
...@@ -62,7 +62,13 @@ func mount(mountPoint string, options string) (fd int, err error) { ...@@ -62,7 +62,13 @@ func mount(mountPoint string, options string) (fd int, err error) {
return return
} }
return getConnection(local) fd, err = getConnection(local)
if err != nil {
return -1, err
}
close(ready)
return fd, err
} }
func privilegedUnmount(mountPoint string) error { func privilegedUnmount(mountPoint string) error {
......
...@@ -68,8 +68,10 @@ func TestNoOpen(t *testing.T) { ...@@ -68,8 +68,10 @@ func TestNoOpen(t *testing.T) {
} }
defer s.Unmount() defer s.Unmount()
go s.Serve() go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
s.WaitMount()
if s.KernelSettings().Minor < 23 { if s.KernelSettings().Minor < 23 {
t.Skip("Kernel does not support open-less read/writes. Skipping test.") t.Skip("Kernel does not support open-less read/writes. Skipping test.")
} }
...@@ -109,6 +111,9 @@ func TestNodeRead(t *testing.T) { ...@@ -109,6 +111,9 @@ func TestNodeRead(t *testing.T) {
} }
defer s.Unmount() defer s.Unmount()
go s.Serve() go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
content, err := ioutil.ReadFile(dir + "/file") content, err := ioutil.ReadFile(dir + "/file")
if err != nil { if err != nil {
t.Fatalf("ReadFile: %v", err) t.Fatalf("ReadFile: %v", err)
......
...@@ -36,18 +36,21 @@ func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) { ...@@ -36,18 +36,21 @@ func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) {
// Unthreaded, but in background. // Unthreaded, but in background.
go state.Serve() go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return mnt, root, func() { return mnt, root, func() {
state.Unmount() state.Unmount()
os.RemoveAll(tmp) os.RemoveAll(tmp)
} }
} }
func TestMemNodeFsWrite(t *testing.T) { func TestMemNodeFsWrite(t *testing.T) {
wd, _, clean := setupMemNodeTest(t) wd, _, clean := setupMemNodeTest(t)
defer clean() defer clean()
want := "hello" want := "hello"
err := ioutil.WriteFile(wd+"/test", []byte(want), 0644) err := ioutil.WriteFile(wd+"/test", []byte(want), 0644)
if err != nil { if err != nil {
t.Fatalf("WriteFile failed: %v", err) t.Fatalf("WriteFile failed: %v", err)
...@@ -59,7 +62,7 @@ func TestMemNodeFsWrite(t *testing.T) { ...@@ -59,7 +62,7 @@ func TestMemNodeFsWrite(t *testing.T) {
} }
} }
func TestMemNodeFs(t *testing.T) { func TestMemNodeFsBasic(t *testing.T) {
wd, _, clean := setupMemNodeTest(t) wd, _, clean := setupMemNodeTest(t)
defer clean() defer clean()
......
...@@ -40,6 +40,9 @@ func setupOwnerTest(t *testing.T, opts *nodefs.Options) (workdir string, cleanup ...@@ -40,6 +40,9 @@ func setupOwnerTest(t *testing.T, opts *nodefs.Options) (workdir string, cleanup
t.Fatalf("MountNodeFileSystem failed: %v", err) t.Fatalf("MountNodeFileSystem failed: %v", err)
} }
go state.Serve() go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return wd, func() { return wd, func() {
state.Unmount() state.Unmount()
os.RemoveAll(wd) os.RemoveAll(wd)
......
...@@ -47,6 +47,8 @@ type Server struct { ...@@ -47,6 +47,8 @@ type Server struct {
singleReader bool singleReader bool
canSplice bool canSplice bool
loops sync.WaitGroup loops sync.WaitGroup
ready chan error
} }
// SetDebug is deprecated. Use MountOptions.Debug instead. // SetDebug is deprecated. Use MountOptions.Debug instead.
...@@ -173,7 +175,8 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -173,7 +175,8 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
} }
mountPoint = filepath.Clean(filepath.Join(cwd, mountPoint)) mountPoint = filepath.Clean(filepath.Join(cwd, mountPoint))
} }
fd, err := mount(mountPoint, strings.Join(optStrs, ",")) ms.ready = make(chan error, 1)
fd, err := mount(mountPoint, strings.Join(optStrs, ","), ms.ready)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -181,8 +184,11 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -181,8 +184,11 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
ms.mountPoint = mountPoint ms.mountPoint = mountPoint
ms.mountFd = fd ms.mountFd = fd
ms.handleInit() if code := ms.handleInit(); !code.Ok() {
syscall.Close(fd)
// TODO - unmount as well?
return nil, fmt.Errorf("init: %s", code)
}
return ms, nil return ms, nil
} }
...@@ -308,7 +314,7 @@ func (ms *Server) Serve() { ...@@ -308,7 +314,7 @@ func (ms *Server) Serve() {
ms.writeMu.Unlock() ms.writeMu.Unlock()
} }
func (ms *Server) handleInit() { func (ms *Server) handleInit() Status {
// The first request should be INIT; read it synchronously, // The first request should be INIT; read it synchronously,
// and don't spawn new readers. // and don't spawn new readers.
orig := ms.singleReader orig := ms.singleReader
...@@ -317,13 +323,16 @@ func (ms *Server) handleInit() { ...@@ -317,13 +323,16 @@ func (ms *Server) handleInit() {
ms.singleReader = orig ms.singleReader = orig
if errNo != OK || req == nil { if errNo != OK || req == nil {
return return errNo
}
if code := ms.handleRequest(req); !code.Ok() {
return code
} }
ms.handleRequest(req)
// INIT is handled. Init the file system, but don't accept // INIT is handled. Init the file system, but don't accept
// incoming requests, so the file system can setup itself. // incoming requests, so the file system can setup itself.
ms.fileSystem.Init(ms) ms.fileSystem.Init(ms)
return OK
} }
func (ms *Server) loop(exitIdle bool) { func (ms *Server) loop(exitIdle bool) {
...@@ -354,7 +363,7 @@ exit: ...@@ -354,7 +363,7 @@ exit:
} }
} }
func (ms *Server) handleRequest(req *request) { func (ms *Server) handleRequest(req *request) Status {
req.parse() req.parse()
if req.handler == nil { if req.handler == nil {
req.status = ENOSYS req.status = ENOSYS
...@@ -379,6 +388,7 @@ func (ms *Server) handleRequest(req *request) { ...@@ -379,6 +388,7 @@ func (ms *Server) handleRequest(req *request) {
errNo, operationName(req.inHeader.Opcode)) errNo, operationName(req.inHeader.Opcode))
} }
ms.returnRequest(req) ms.returnRequest(req)
return Status(errNo)
} }
func (ms *Server) allocOut(req *request, size uint32) []byte { func (ms *Server) allocOut(req *request, size uint32) []byte {
...@@ -524,8 +534,10 @@ func init() { ...@@ -524,8 +534,10 @@ func init() {
} }
// WaitMount waits for the first request to be served. Use this to // WaitMount waits for the first request to be served. Use this to
// avoid racing between accessing the (empty) mountpoint, and the OS // avoid racing between accessing the (empty or not yet mounted)
// trying to setup the user-space mount. // mountpoint, and the OS trying to setup the user-space mount.
func (ms *Server) WaitMount() { // Currently, this call only necessary on OSX.
// TODO(hanwen): see if this can safely be removed. func (ms *Server) WaitMount() error {
err := <-ms.ready
return err
} }
...@@ -48,7 +48,9 @@ func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) { ...@@ -48,7 +48,9 @@ func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) {
t.Fatalf("MountNodeFileSystem failed: %v", err) t.Fatalf("MountNodeFileSystem failed: %v", err)
} }
go state.Serve() go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return dir, pfs, func() { return dir, pfs, func() {
err := state.Unmount() err := state.Unmount()
if err == nil { if err == nil {
...@@ -149,6 +151,9 @@ func TestNonseekable(t *testing.T) { ...@@ -149,6 +151,9 @@ func TestNonseekable(t *testing.T) {
defer state.Unmount() defer state.Unmount()
go state.Serve() go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
f, err := os.Open(dir + "/file") f, err := os.Open(dir + "/file")
if err != nil { if err != nil {
...@@ -180,6 +185,9 @@ func TestGetAttrRace(t *testing.T) { ...@@ -180,6 +185,9 @@ func TestGetAttrRace(t *testing.T) {
t.Fatalf("MountNodeFileSystem failed: %v", err) t.Fatalf("MountNodeFileSystem failed: %v", err)
} }
go state.Serve() go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
defer state.Unmount() defer state.Unmount()
......
...@@ -49,7 +49,9 @@ func defaultReadTest(t *testing.T) (root string, cleanup func()) { ...@@ -49,7 +49,9 @@ func defaultReadTest(t *testing.T) (root string, cleanup func()) {
t.Fatalf("MountNodeFileSystem failed: %v", err) t.Fatalf("MountNodeFileSystem failed: %v", err)
} }
go state.Serve() go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return dir, func() { return dir, func() {
state.Unmount() state.Unmount()
os.Remove(dir) os.Remove(dir)
......
...@@ -51,6 +51,9 @@ func TestDeleteNotify(t *testing.T) { ...@@ -51,6 +51,9 @@ func TestDeleteNotify(t *testing.T) {
} }
go state.Serve() go state.Serve()
defer state.Unmount() defer state.Unmount()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
_, code := root.Mkdir("testdir", 0755, nil) _, code := root.Mkdir("testdir", 0755, nil)
if !code.Ok() { if !code.Ok() {
......
...@@ -153,6 +153,9 @@ func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func( ...@@ -153,6 +153,9 @@ func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func(
} }
go state.Serve() go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
// Trigger INIT. // Trigger INIT.
os.Lstat(dir) os.Lstat(dir)
......
...@@ -107,10 +107,10 @@ func NewTestCase(t *testing.T) *testCase { ...@@ -107,10 +107,10 @@ func NewTestCase(t *testing.T) *testCase {
t.Fatal("NewServer:", err) t.Fatal("NewServer:", err)
} }
// Unthreaded, but in background.
go tc.state.Serve() go tc.state.Serve()
if err := tc.state.WaitMount(); err != nil {
tc.state.WaitMount() t.Fatal("WaitMount", err)
}
return tc return tc
} }
...@@ -875,6 +875,9 @@ func TestOriginalIsSymlink(t *testing.T) { ...@@ -875,6 +875,9 @@ func TestOriginalIsSymlink(t *testing.T) {
defer state.Unmount() defer state.Unmount()
go state.Serve() go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
if _, err := os.Lstat(mnt); err != nil { if _, err := os.Lstat(mnt); err != nil {
t.Fatalf("Lstat failed: %v", err) t.Fatalf("Lstat failed: %v", err)
......
...@@ -176,6 +176,9 @@ func TestDefaultNodeMount(t *testing.T) { ...@@ -176,6 +176,9 @@ func TestDefaultNodeMount(t *testing.T) {
t.Fatalf("MountRoot: %v", err) t.Fatalf("MountRoot: %v", err)
} }
go s.Serve() go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
defer s.Unmount() defer s.Unmount()
if err := conn.Mount(root.Inode(), "sub", nodefs.NewDefaultNode(), nil); !err.Ok() { if err := conn.Mount(root.Inode(), "sub", nodefs.NewDefaultNode(), nil); !err.Ok() {
......
...@@ -93,6 +93,9 @@ func NewNotifyTest(t *testing.T) *NotifyTest { ...@@ -93,6 +93,9 @@ func NewNotifyTest(t *testing.T) *NotifyTest {
t.Fatalf("MountNodeFileSystem failed: %v", err) t.Fatalf("MountNodeFileSystem failed: %v", err)
} }
go me.state.Serve() go me.state.Serve()
if err := me.state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return me return me
} }
......
...@@ -49,6 +49,10 @@ func TestDefaultXAttr(t *testing.T) { ...@@ -49,6 +49,10 @@ func TestDefaultXAttr(t *testing.T) {
t.Fatalf("MountRoot: %v", err) t.Fatalf("MountRoot: %v", err)
} }
go s.Serve() go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
defer s.Unmount() defer s.Unmount()
var data [1024]byte var data [1024]byte
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment