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
/*
// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
// which carries this notice:
//
// The files in this directory are subject to the following license.
//
// 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;
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"
)
if(getvfsbyname("osxfusefs", &vfs) < 0){
if(access(f="/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs", 0) < 0){
*err = strdup("cannot find load_fusefs");
return -1;
}
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;
}
func openFUSEDevice() (*os.File, error) {
fs, err := filepath.Glob("/dev/osxfuse*")
if err != nil {
return nil, err
}
// Look for available FUSE device.
for(i=0;; i++){
snprintf(buf, sizeof buf, "/dev/osxfuse%d", i);
if(access(buf, 0) < 0){
*err = strdup("no available fuse devices");
return -1;
if len(fs) == 0 {
// TODO(hanwen): run the load_osxfuse command.
return nil, fmt.Errorf("no FUSE devices found")
}
for _, fn := range fs {
f, err := os.OpenFile(fn, os.O_RDWR, 0)
if err != nil {
continue
}
if((fd = open(buf, O_RDWR)) >= 0)
break;
return f, nil
}
pid = fork();
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;
return nil, fmt.Errorf("all FUSE devices busy")
}
*/
import "C"
import (
"fmt"
"log"
"os/exec"
"syscall"
"unsafe"
)
const bin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
func mount(dir string, options string) (int, error) {
errp := (**C.char)(C.malloc(16))
*errp = nil
defer C.free(unsafe.Pointer(errp))
cdir := C.CString(dir)
defer C.free(unsafe.Pointer(cdir))
fd := C.mountfuse(cdir, errp)
if *errp != nil {
return -1, mountError(C.GoString(*errp))
func mount(mountPoint, options string, ready chan<- error) (fd int, err error) {
f, err := openFUSEDevice()
if err != nil {
return 0, err
}
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 {
return string(m)
}
var out, errOut bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &errOut
func unmount(mountPoint string) error {
if err := syscall.Unmount(mountPoint, 0); err != nil {
return fmt.Errorf("umount(%q): %v", mountPoint, err)
if err := cmd.Start(); err != nil {
f.Close()
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() {
var err error
umountBinary, err = exec.LookPath("umount")
if err != nil {
log.Fatalf("Could not find umount binary: %v", err)
}
func unmount(dir string) error {
return syscall.Unmount(dir, 0)
}
......@@ -24,7 +24,7 @@ func unixgramSocketpair() (l, r *os.File, err error) {
// Create a FUSE FS on the specified mount point. The returned
// 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()
if err != nil {
return
......@@ -62,7 +62,13 @@ func mount(mountPoint string, options string) (fd int, err error) {
return
}
return getConnection(local)
fd, err = getConnection(local)
if err != nil {
return -1, err
}
close(ready)
return fd, err
}
func privilegedUnmount(mountPoint string) error {
......
......@@ -68,8 +68,10 @@ func TestNoOpen(t *testing.T) {
}
defer s.Unmount()
go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
s.WaitMount()
if s.KernelSettings().Minor < 23 {
t.Skip("Kernel does not support open-less read/writes. Skipping test.")
}
......@@ -109,6 +111,9 @@ func TestNodeRead(t *testing.T) {
}
defer s.Unmount()
go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
content, err := ioutil.ReadFile(dir + "/file")
if err != nil {
t.Fatalf("ReadFile: %v", err)
......
......@@ -36,18 +36,21 @@ func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) {
// Unthreaded, but in background.
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return mnt, root, func() {
state.Unmount()
os.RemoveAll(tmp)
}
}
func TestMemNodeFsWrite(t *testing.T) {
wd, _, clean := setupMemNodeTest(t)
defer clean()
want := "hello"
err := ioutil.WriteFile(wd+"/test", []byte(want), 0644)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
......@@ -59,7 +62,7 @@ func TestMemNodeFsWrite(t *testing.T) {
}
}
func TestMemNodeFs(t *testing.T) {
func TestMemNodeFsBasic(t *testing.T) {
wd, _, clean := setupMemNodeTest(t)
defer clean()
......
......@@ -40,6 +40,9 @@ func setupOwnerTest(t *testing.T, opts *nodefs.Options) (workdir string, cleanup
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return wd, func() {
state.Unmount()
os.RemoveAll(wd)
......
......@@ -47,6 +47,8 @@ type Server struct {
singleReader bool
canSplice bool
loops sync.WaitGroup
ready chan error
}
// SetDebug is deprecated. Use MountOptions.Debug instead.
......@@ -173,7 +175,8 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
}
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 {
return nil, err
}
......@@ -181,8 +184,11 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
ms.mountPoint = mountPoint
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
}
......@@ -308,7 +314,7 @@ func (ms *Server) Serve() {
ms.writeMu.Unlock()
}
func (ms *Server) handleInit() {
func (ms *Server) handleInit() Status {
// The first request should be INIT; read it synchronously,
// and don't spawn new readers.
orig := ms.singleReader
......@@ -317,13 +323,16 @@ func (ms *Server) handleInit() {
ms.singleReader = orig
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
// incoming requests, so the file system can setup itself.
ms.fileSystem.Init(ms)
return OK
}
func (ms *Server) loop(exitIdle bool) {
......@@ -354,7 +363,7 @@ exit:
}
}
func (ms *Server) handleRequest(req *request) {
func (ms *Server) handleRequest(req *request) Status {
req.parse()
if req.handler == nil {
req.status = ENOSYS
......@@ -379,6 +388,7 @@ func (ms *Server) handleRequest(req *request) {
errNo, operationName(req.inHeader.Opcode))
}
ms.returnRequest(req)
return Status(errNo)
}
func (ms *Server) allocOut(req *request, size uint32) []byte {
......@@ -524,8 +534,10 @@ func init() {
}
// WaitMount waits for the first request to be served. Use this to
// avoid racing between accessing the (empty) mountpoint, and the OS
// trying to setup the user-space mount.
func (ms *Server) WaitMount() {
// TODO(hanwen): see if this can safely be removed.
// avoid racing between accessing the (empty or not yet mounted)
// mountpoint, and the OS trying to setup the user-space mount.
// Currently, this call only necessary on OSX.
func (ms *Server) WaitMount() error {
err := <-ms.ready
return err
}
......@@ -48,7 +48,9 @@ func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return dir, pfs, func() {
err := state.Unmount()
if err == nil {
......@@ -149,6 +151,9 @@ func TestNonseekable(t *testing.T) {
defer state.Unmount()
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
f, err := os.Open(dir + "/file")
if err != nil {
......@@ -180,6 +185,9 @@ func TestGetAttrRace(t *testing.T) {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
defer state.Unmount()
......
......@@ -49,7 +49,9 @@ func defaultReadTest(t *testing.T) (root string, cleanup func()) {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return dir, func() {
state.Unmount()
os.Remove(dir)
......
......@@ -51,6 +51,9 @@ func TestDeleteNotify(t *testing.T) {
}
go state.Serve()
defer state.Unmount()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
_, code := root.Mkdir("testdir", 0755, nil)
if !code.Ok() {
......
......@@ -153,6 +153,9 @@ func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func(
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
// Trigger INIT.
os.Lstat(dir)
......
......@@ -107,10 +107,10 @@ func NewTestCase(t *testing.T) *testCase {
t.Fatal("NewServer:", err)
}
// Unthreaded, but in background.
go tc.state.Serve()
tc.state.WaitMount()
if err := tc.state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return tc
}
......@@ -875,6 +875,9 @@ func TestOriginalIsSymlink(t *testing.T) {
defer state.Unmount()
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
if _, err := os.Lstat(mnt); err != nil {
t.Fatalf("Lstat failed: %v", err)
......
......@@ -176,6 +176,9 @@ func TestDefaultNodeMount(t *testing.T) {
t.Fatalf("MountRoot: %v", err)
}
go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
defer s.Unmount()
if err := conn.Mount(root.Inode(), "sub", nodefs.NewDefaultNode(), nil); !err.Ok() {
......
......@@ -93,6 +93,9 @@ func NewNotifyTest(t *testing.T) *NotifyTest {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go me.state.Serve()
if err := me.state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return me
}
......
......@@ -49,6 +49,10 @@ func TestDefaultXAttr(t *testing.T) {
t.Fatalf("MountRoot: %v", err)
}
go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
defer s.Unmount()
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