Commit 9bad5c77 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys Committed by Han-Wen Nienhuys

Rewrite Go FUSE library.

* Introduce RawFileSystem as direct interface to FUSE kernel layer,
  similar to fuse_lowlevel.h

* Introduce PathFuseFilesystem as path based interface, similar to
  fuse.h.

* Add connector to hook up PathFuseFilesystem with RawFileSystem
  interface.

* Add PassThroughFuse, which exposes os and syscall APIs as a FUSE
  PathFuseFilesystem, for testing.

* Add passthrough_test.go which exercises all of the API.

* Add fuse/README with explicit license notice and some information.

* Remove old 'manager' based API.
parent da579c85
*~
*.6
.nfs*
_*
# Use "gomake install" to build and install this package.
include $(GOROOT)/src/Make.inc
TARG=github.com/krasin/go-fuse-zip/fuse
#TARG=github.com/krasin/go-fuse-zip/fuse
TARG=fuse
GOFILES=\
GOFILES=misc.go\
fuse.go\
mount.go\
types.go\
dummyfuse.go\
pathfilesystem.go \
passthrough.go\
include $(GOROOT)/src/Make.pkg
INTRODUCTION
Basic bindings for the FUSE.
This library communicates directly with the kernel device, rather than
linking libfuse. It does not use splice syscalls for transferring data.
LICENSE
This library is distributed under the license governing the Go source
code, see
http://code.google.com/p/go/source/browse/LICENSE
SUPPORTED
See passthrough_test.go for a test that exercises available
functionality.
BUGS
Yes, probably. Report them through golang-nuts@googlegroups.com.
KNOWN PROBLEMS
Grep source code for TODO. Major topics:
* Missing support for file locking: FUSE_GETLK, FUSE_SETLK, FUSE_SETLKW
* Missing support for extended attributes FUSE_SETXATTR, FUSE_GETXATTR, FUSE_LISTXATTR
* Missing support for FUSE_INTERRUPT, FUSE_NOTIFY, CUSE, BMAP, POLL, IOCTL
* Missing support for doing GetAttr/SetAttr on opened files.
package fuse
// Declare dummy methods, for cut & paste convenience.
type DummyFuse struct{}
func (fs *DummyFuse) Init(h *InHeader, input *InitIn) (*InitOut, Status) {
return new(InitOut), OK
}
func (fs *DummyFuse) Destroy(h *InHeader, input *InitIn) {
}
func (fs *DummyFuse) Lookup(h *InHeader, name string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
func (fs *DummyFuse) Forget(h *InHeader, input *ForgetIn) {
}
func (fs *DummyFuse) GetAttr(header *InHeader, input *GetAttrIn) (out *AttrOut, code Status) {
return nil, ENOSYS
}
func (fs *DummyFuse) Open(header *InHeader, input *OpenIn) (flags uint32, fuseFile RawFuseFile, status Status) {
return 0, nil, OK
}
func (self *DummyFuse) SetAttr(header *InHeader, input *SetAttrIn) (out *AttrOut, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) Readlink(header *InHeader) (out []byte, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) Mknod(header *InHeader, input *MknodIn, name string) (out *EntryOut, code Status) {
return new(EntryOut), ENOSYS
}
func (self *DummyFuse) Mkdir(header *InHeader, input *MkdirIn, name string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) Unlink(header *InHeader, name string) (code Status) {
return ENOSYS
}
func (self *DummyFuse) Rmdir(header *InHeader, name string) (code Status) {
return ENOSYS
}
func (self *DummyFuse) Symlink(header *InHeader, pointedTo string, linkName string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) Rename(header *InHeader, input *RenameIn, oldName string, newName string) (code Status) {
return ENOSYS
}
func (self *DummyFuse) Link(header *InHeader, input *LinkIn, name string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) SetXAttr(header *InHeader, input *SetXAttrIn) Status {
return ENOSYS
}
func (self *DummyFuse) GetXAttr(header *InHeader, input *GetXAttrIn) (out *GetXAttrOut, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) Access(header *InHeader, input *AccessIn) (code Status) {
return ENOSYS
}
func (self *DummyFuse) Create(header *InHeader, input *CreateIn, name string) (flags uint32, fuseFile RawFuseFile, out *EntryOut, code Status) {
return 0, nil, nil, ENOSYS
}
func (self *DummyFuse) Bmap(header *InHeader, input *BmapIn) (out *BmapOut, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) Ioctl(header *InHeader, input *IoctlIn) (out *IoctlOut, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) Poll(header *InHeader, input *PollIn) (out *PollOut, code Status) {
return nil, ENOSYS
}
func (self *DummyFuse) OpenDir(header *InHeader, input *OpenIn) (flags uint32, fuseFile RawFuseDir, status Status) {
return 0, nil, ENOSYS
}
////////////////////////////////////////////////////////////////
// DummyFuseFile
type DummyFuseFile struct{}
func (self *DummyFuseFile) Read(*ReadIn) ([]byte, Status) {
return []byte(""), ENOSYS
}
func (self *DummyFuseFile) Write(*WriteIn, []byte) (uint32, Status) {
return 0, ENOSYS
}
func (self *DummyFuseFile) Flush() {
}
func (self *DummyFuseFile) Release() {
}
func (self *DummyFuseFile) Fsync(*FsyncIn) (code Status) {
return ENOSYS
}
func (self *DummyFuseFile) ReadDir(input *ReadIn) (*DEntryList, Status) {
return nil, ENOSYS
}
func (self *DummyFuseFile) ReleaseDir() {
}
func (self *DummyFuseFile) FsyncDir(input *FsyncIn) (code Status) {
return ENOSYS
}
////////////////////////////////////////////////////////////////
// DummyPathFuse
type DummyPathFuse struct {}
func (self *DummyPathFuse) GetAttr(name string) (*Attr, Status) {
return nil, ENOSYS
}
func (self *DummyPathFuse) Readlink(name string) (string, Status) {
return "", ENOSYS
}
func (self *DummyPathFuse) Mknod(name string, mode uint32, dev uint32) Status {
return ENOSYS
}
func (self *DummyPathFuse) Mkdir(name string, mode uint32) Status {
return ENOSYS
}
func (self *DummyPathFuse) Unlink(name string) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Rmdir(name string) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Symlink(value string, linkName string) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Rename(oldName string, newName string) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Link(oldName string, newName string) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Chmod(name string, mode uint32) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Chown(name string, uid uint32, gid uint32) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Truncate(name string, offset uint64) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Open(name string, flags uint32) (file RawFuseFile, code Status) {
return nil, ENOSYS
}
func (self *DummyPathFuse) OpenDir(name string) (dir RawFuseDir, code Status) {
return nil, ENOSYS
}
func (self *DummyPathFuse) Init() (*InitOut, Status) {
return nil, ENOSYS
}
func (self *DummyPathFuse) Destroy() {
}
func (self *DummyPathFuse) Access(name string, mode uint32) (code Status) {
return ENOSYS
}
func (self *DummyPathFuse) Create(name string, flags uint32, mode uint32) (file RawFuseFile, code Status) {
return nil, ENOSYS
}
func (self *DummyPathFuse) Utimens(name string, AtimeNs uint64, CtimeNs uint64) (code Status) {
return ENOSYS
}
package fuse
// Compilation test for DummyFuse and DummyPathFuse
import (
"testing"
)
func TestDummy(t *testing.T) {
fs := new(DummyFuse)
NewMountState(fs)
pathFs := new(DummyPathFuse)
NewPathFileSystemConnector(pathFs)
}
......@@ -4,150 +4,385 @@ import (
"bytes"
"encoding/binary"
"fmt"
"log"
"os"
"path"
"strings"
"sync"
"syscall"
)
const (
bufSize = 66000
)
type File interface {
ReadAt(p []byte, off int64) (n int, err os.Error)
Close() (status Status)
type Empty interface {
}
////////////////////////////////////////////////////////////////
// State related to this mount point.
type MountState struct {
// We should store the RawFuseFile/Dirs on the Go side,
// otherwise our files may be GCd. Here, the index is the Fh
// field
openedFiles map[uint64] RawFuseFile
openedFilesMutex sync.RWMutex
nextFreeFile uint64
openedDirs map[uint64] RawFuseDir
openedDirsMutex sync.RWMutex
nextFreeDir uint64
// Empty if unmounted.
mountPoint string
fileSystem RawFileSystem
// I/O with kernel and daemon.
mountFile *os.File
errorChannel chan os.Error
outputChannel chan [][]byte
// Run each operation in its own Go-routine.
threaded bool
// Dump debug info onto stdout.
Debug bool
}
func (self *MountState) RegisterFile(file RawFuseFile) uint64 {
self.openedFilesMutex.Lock()
defer self.openedFilesMutex.Unlock()
// We will be screwed if nextFree ever wraps.
self.nextFreeFile++
index := self.nextFreeFile
self.openedFiles[index] = file
return index
}
type FileSystem interface {
List(parent string) (names []string, status Status)
GetAttr(path string) (out Attr, status Status)
Open(path string) (file File, status Status)
func (self *MountState) FindFile(index uint64) RawFuseFile {
self.openedFilesMutex.RLock()
defer self.openedFilesMutex.RUnlock()
return self.openedFiles[index]
}
type Mounted interface {
Unmount() (err os.Error)
func (self *MountState) UnregisterFile(handle uint64) {
self.openedFilesMutex.Lock()
defer self.openedFilesMutex.Unlock()
self.openedFiles[handle] = nil, false
}
func Mount(mountPoint string, fs FileSystem) (m Mounted, err os.Error, errors chan os.Error) {
f, m, err := mount(mountPoint)
func (self *MountState) RegisterDir(dir RawFuseDir) uint64 {
self.openedDirsMutex.Lock()
defer self.openedDirsMutex.Unlock()
self.nextFreeDir++
index := self.nextFreeDir
self.openedDirs[index] = dir
return index
}
func (self *MountState) FindDir(index uint64) RawFuseDir {
self.openedDirsMutex.RLock()
defer self.openedDirsMutex.RUnlock()
return self.openedDirs[index]
}
func (self *MountState) UnregisterDir(handle uint64) {
self.openedDirsMutex.Lock()
defer self.openedDirsMutex.Unlock()
self.openedDirs[handle] = nil, false
}
// Mount filesystem on mountPoint.
//
// If threaded is set, each filesystem operation executes in a
// separate goroutine, and errors and writes are done asynchronously
// using channels.
//
// TODO - error handling should perhaps be user-serviceable.
func (self *MountState) Mount(mountPoint string, threaded bool) os.Error {
file, mp, err := mount(mountPoint)
if err != nil {
return
return err
}
errors = make(chan os.Error, 100)
go loop(f, fs, errors)
return
self.mountPoint = mp
self.mountFile = file
self.threaded = threaded
if self.threaded {
self.outputChannel = make(chan [][]byte, 100)
self.errorChannel = make(chan os.Error, 100)
go self.asyncWriterThread()
go self.DefaultErrorHandler()
}
go self.loop()
return nil
}
func loop(f *os.File, fs FileSystem, errors chan os.Error) {
buf := make([]byte, bufSize)
defer close(errors)
toW := make(chan [][]byte, 100)
defer close(toW)
go writer(f, toW, errors)
managerReq := make(chan *managerRequest, 100)
startManager(fs, managerReq)
c := &managerClient{managerReq}
defer close(managerReq)
for {
n, err := f.Read(buf)
func (self *MountState) Unmount() os.Error {
// Todo: flush/release all files/dirs?
result := unmount(self.mountPoint)
if result == nil {
self.mountPoint = ""
}
return result
}
func (self *MountState) DefaultErrorHandler() {
for err := range self.errorChannel {
if err == os.EOF {
break
}
log.Println("error: ", err)
}
}
func (self *MountState) Error(err os.Error) {
// It is safe to do errors unthreaded, since the logger is thread-safe.
if self.Debug || self.threaded {
log.Println("error: ", err)
} else {
self.errorChannel <- err
}
}
func (self *MountState) Write(packet [][]byte) {
if packet == nil {
return
}
if self.threaded {
self.outputChannel <- packet
} else {
self.syncWrite(packet)
}
}
func NewMountState(fs RawFileSystem) *MountState {
self := new(MountState)
self.openedDirs = make(map[uint64] RawFuseDir)
self.openedFiles = make(map[uint64] RawFuseFile)
self.mountPoint = ""
self.fileSystem = fs
return self
}
////////////////
// Private routines.
func (self *MountState) asyncWriterThread() {
for packet := range self.outputChannel {
self.syncWrite(packet)
}
}
func (self *MountState) syncWrite(packet [][]byte) {
_, err := Writev(self.mountFile.Fd(), packet)
if err != nil {
self.Error(os.NewError(fmt.Sprintf("writer: Writev %v failed, err: %v", packet, err)))
}
}
////////////////////////////////////////////////////////////////
// Logic for the control loop.
func (self *MountState) loop() {
buf := make([]byte, bufSize)
// See fuse_kern_chan_receive()
for {
n, err := self.mountFile.Read(buf)
if err != nil {
errors <- os.NewError(fmt.Sprintf("Failed to read from fuse conn: %v", err))
errNo := OsErrorToFuseError(err)
// Retry.
if errNo == syscall.ENOENT {
continue
}
// According to fuse_chan_receive()
if errNo == syscall.ENODEV {
break;
}
// What I see on linux-x86 2.6.35.10.
if errNo == syscall.ENOSYS {
break;
}
readErr := os.NewError(fmt.Sprintf("Failed to read from fuse conn: %v", err))
self.Error(readErr)
break
}
go handle(fs, buf[0:n], c, toW, errors)
if self.threaded && !self.Debug {
go self.handle(buf[0:n])
} else {
self.handle(buf[0:n])
}
}
self.mountFile.Close()
if self.threaded {
close(self.outputChannel)
close(self.errorChannel)
}
}
func handle(fs FileSystem, in_data []byte, c *managerClient, toW chan [][]byte, errors chan os.Error) {
fmt.Printf("in_data: %v\n", in_data)
func (self *MountState) handle(in_data []byte) {
r := bytes.NewBuffer(in_data)
h := new(InHeader)
err := binary.Read(r, binary.LittleEndian, h)
header := new(InHeader)
err := binary.Read(r, binary.LittleEndian, header)
if err == os.EOF {
err = os.NewError(fmt.Sprintf("MountPoint, handle: can't read a header, in_data: %v", in_data))
}
if err != nil {
errors <- err
self.Error(err)
return
}
out := dispatch(fs, h, r, c, errors)
if out == nil {
fmt.Printf("out is nil")
return
}
fmt.Printf("Sending to writer: %v\n", out)
toW <- out
self.Write(dispatch(self, header, r))
}
func dispatch(fs FileSystem, h *InHeader, r *bytes.Buffer, c *managerClient, errors chan os.Error) (out [][]byte) {
fmt.Printf("Opcode: %v, NodeId: %v, h: %v\n", h.Opcode, h.NodeId, h)
func dispatch(state *MountState, h *InHeader, arg *bytes.Buffer) (outBytes [][]byte) {
input := newInput(h.Opcode)
if input != nil && !parseLittleEndian(arg, input) {
return serialize(h, EIO, nil)
}
var out Empty
var status Status
out = nil
status = OK
fs := state.fileSystem
filename := ""
// Perhaps a map is faster?
if (h.Opcode == FUSE_UNLINK || h.Opcode == FUSE_RMDIR ||
h.Opcode == FUSE_LOOKUP || h.Opcode == FUSE_MKDIR ||
h.Opcode == FUSE_MKNOD || h.Opcode == FUSE_CREATE ||
h.Opcode == FUSE_LINK) {
filename = strings.TrimRight(string(arg.Bytes()), "\x00")
}
if state.Debug {
log.Printf("Dispatch: %v, NodeId: %v, n: %v\n", operationName(h.Opcode), h.NodeId, filename)
}
// Follow ordering of fuse_lowlevel.h.
switch h.Opcode {
case FUSE_INIT:
return parseInvoke(initFuse, fs, h, r, c, new(InitIn))
out, status = initFuse(state, h, input.(*InitIn))
case FUSE_DESTROY:
fs.Destroy(h, input.(*InitIn))
case FUSE_LOOKUP:
out, status = fs.Lookup(h, filename)
case FUSE_FORGET:
fs.Forget(h, input.(*ForgetIn))
// If we try to write OK, nil, we will get
// error: writer: Writev [[16 0 0 0 0 0 0 0 17 0 0 0 0 0 0 0]]
// failed, err: writev: no such file or directory
return nil
case FUSE_GETATTR:
return parseInvoke(getAttr, fs, h, r, c, new(GetAttrIn))
case FUSE_GETXATTR:
return parseInvoke(getXAttr, fs, h, r, c, new(GetXAttrIn))
case FUSE_OPENDIR:
return parseInvoke(openDir, fs, h, r, c, new(OpenIn))
case FUSE_READDIR:
return parseInvoke(readDir, fs, h, r, c, new(ReadIn))
case FUSE_LOOKUP:
out, status := lookup(h, r, c)
return serialize(h, status, out)
case FUSE_RELEASEDIR:
return parseInvoke(releaseDir, fs, h, r, c, new(ReleaseIn))
// TODO - if input.Fh is set, do file.GetAttr
out, status = fs.GetAttr(h, input.(*GetAttrIn))
case FUSE_SETATTR:
out, status = doSetattr(state, h, input.(*SetAttrIn))
case FUSE_READLINK:
out, status = fs.Readlink(h)
case FUSE_MKNOD:
out, status = fs.Mknod(h, input.(*MknodIn), filename)
case FUSE_MKDIR:
out, status = fs.Mkdir(h, input.(*MkdirIn), filename)
case FUSE_UNLINK:
status = fs.Unlink(h, filename)
case FUSE_RMDIR:
status = fs.Rmdir(h, filename)
case FUSE_SYMLINK:
filenames := strings.Split(string(arg.Bytes()), "\x00", 3)
if len(filenames) >= 2 {
out, status = fs.Symlink(h, filenames[1], filenames[0])
} else {
status = EIO
}
case FUSE_RENAME:
filenames := strings.Split(string(arg.Bytes()), "\x00", 3)
if len(filenames) >= 2 {
status = fs.Rename(h, input.(*RenameIn), filenames[0], filenames[1])
} else {
status = EIO
}
case FUSE_LINK:
out, status = fs.Link(h, input.(*LinkIn), filename)
case FUSE_OPEN:
return parseInvoke(open, fs, h, r, c, new(OpenIn))
out, status = doOpen(state, h, input.(*OpenIn))
case FUSE_READ:
return parseInvoke(read, fs, h, r, c, new(ReadIn))
out, status = doRead(state, h, input.(*ReadIn))
case FUSE_WRITE:
out, status = doWrite(state, h, input.(*WriteIn), arg.Bytes())
case FUSE_FLUSH:
return parseInvoke(flush, fs, h, r, c, new(FlushIn))
out, status = doFlush(state, h, input.(*FlushIn))
case FUSE_RELEASE:
return parseInvoke(release, fs, h, r, c, new(ReleaseIn))
out, status = doRelease(state, h, input.(*ReleaseIn))
case FUSE_FSYNC:
status = doFsync(state, h, input.(*FsyncIn))
case FUSE_OPENDIR:
out, status = doOpenDir(state, h, input.(*OpenIn))
case FUSE_READDIR:
out, status = doReadDir(state, h, input.(*ReadIn))
case FUSE_RELEASEDIR:
out, status = doReleaseDir(state, h, input.(*ReleaseIn))
case FUSE_FSYNCDIR:
// todo- check input type.
status = doFsyncDir(state, h, input.(*FsyncIn))
// TODO - implement XAttr routines.
// case FUSE_SETXATTR:
// status = fs.SetXAttr(h, input.(*SetXAttrIn))
// case FUSE_GETXATTR:
// out, status = fs.GetXAttr(h, input.(*GetXAttrIn))
// case FUSE_LISTXATTR:
// case FUSE_REMOVEXATTR
case FUSE_ACCESS:
status = fs.Access(h, input.(*AccessIn))
case FUSE_CREATE:
out, status = doCreate(state, h, input.(*CreateIn), filename)
// TODO - implement file locking.
// case FUSE_SETLK
// case FUSE_SETLKW
case FUSE_BMAP:
out, status = fs.Bmap(h, input.(*BmapIn))
case FUSE_IOCTL:
out, status = fs.Ioctl(h, input.(*IoctlIn))
case FUSE_POLL:
out, status = fs.Poll(h, input.(*PollIn))
// TODO - figure out how to support this
// case FUSE_INTERRUPT
default:
errors <- os.NewError(fmt.Sprintf("Unsupported OpCode: %d", h.Opcode))
state.Error(os.NewError(fmt.Sprintf("Unsupported OpCode: %d", h.Opcode)))
return serialize(h, ENOSYS, nil)
}
return
}
func parse(b *bytes.Buffer, data interface{}) bool {
err := binary.Read(b, binary.LittleEndian, data)
if err == nil {
return true
}
if err == os.EOF {
return false
}
panic(fmt.Sprintf("Cannot parse %v", data))
}
type handler func(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (out interface{}, status Status)
func parseInvoke(f handler, fs FileSystem, h *InHeader, b *bytes.Buffer, c *managerClient, ing interface{}) [][]byte {
if parse(b, ing) {
out, status := f(fs, h, ing, c)
if status != OK {
out = nil
}
return serialize(h, status, out)
if state.Debug {
log.Printf("Serialize: %v code: %v value: %v\n",
operationName(h.Opcode), errorString(status), out)
}
return serialize(h, EIO, nil)
return serialize(h, status, out)
}
func serialize(h *InHeader, res Status, out interface{}) (data [][]byte) {
b := new(bytes.Buffer)
out_data := make([]byte, 0)
fmt.Printf("OpCode: %v result: %v\n", h.Opcode, res)
b := new(bytes.Buffer)
if out != nil && res == OK {
fmt.Printf("out = %v, out == nil: %v\n", out, out == nil)
err := binary.Write(b, binary.LittleEndian, out)
if err == nil {
out_data = b.Bytes()
......@@ -155,11 +390,10 @@ func serialize(h *InHeader, res Status, out interface{}) (data [][]byte) {
panic(fmt.Sprintf("Can't serialize out: %v, err: %v", out, err))
}
}
fmt.Printf("out_data: %v, len(out_data): %d, SizeOfOutHeader: %d\n",
out_data, len(out_data), SizeOfOutHeader)
var hout OutHeader
hout.Unique = h.Unique
hout.Status = res
hout.Status = -res
hout.Length = uint32(len(out_data) + SizeOfOutHeader)
b = new(bytes.Buffer)
err := binary.Write(b, binary.LittleEndian, &hout)
......@@ -168,541 +402,158 @@ func serialize(h *InHeader, res Status, out interface{}) (data [][]byte) {
}
_, _ = b.Write(out_data)
data = [][]byte{b.Bytes()}
return
return data
}
func initFuse(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in, _ := ing.(*InitIn)
fmt.Printf("in: %v\n", in)
if in.Major != FUSE_KERNEL_VERSION {
fmt.Printf("Major versions does not match. Given %d, want %d\n", in.Major, FUSE_KERNEL_VERSION)
func initFuse(state* MountState, h *InHeader, input *InitIn) (Empty, Status) {
out, initStatus := state.fileSystem.Init(h, input)
if initStatus != OK {
return nil, initStatus
}
if input.Major != FUSE_KERNEL_VERSION {
fmt.Printf("Major versions does not match. Given %d, want %d\n", input.Major, FUSE_KERNEL_VERSION)
return nil, EIO
}
if in.Minor < FUSE_KERNEL_MINOR_VERSION {
fmt.Printf("Minor version is less than we support. Given %d, want at least %d\n", in.Minor, FUSE_KERNEL_MINOR_VERSION)
if input.Minor < FUSE_KERNEL_MINOR_VERSION {
fmt.Printf("Minor version is less than we support. Given %d, want at least %d\n", input.Minor, FUSE_KERNEL_MINOR_VERSION)
return nil, EIO
}
out := new(InitOut)
out.Major = FUSE_KERNEL_VERSION
out.Minor = FUSE_KERNEL_MINOR_VERSION
out.MaxReadAhead = in.MaxReadAhead
out.MaxReadAhead = input.MaxReadAhead
out.Flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS
out.MaxWrite = 65536
return out, OK
}
func getAttr(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in := ing.(*GetAttrIn)
fmt.Printf("FUSE_GETATTR: %v, Fh: %d\n", in, in.Fh)
out := new(AttrOut)
resp := c.getPath(h.NodeId)
if resp.status != OK {
return nil, resp.status
}
attr, res := fs.GetAttr(resp.path)
if res != OK {
return nil, res
}
out.Attr = attr
out.Ino = h.NodeId
return out, OK
}
func getXAttr(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
out := new(GetXAttrOut)
return out, OK
}
////////////////////////////////////////////////////////////////
// Handling files.
func openDir(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in := ing.(*OpenIn)
fmt.Printf("FUSE_OPENDIR: %v\n", in)
resp := c.openDir(h.NodeId)
if resp.status != OK {
return nil, resp.status
func doOpen(state *MountState, header *InHeader, input *OpenIn) (genericOut Empty, code Status) {
flags, fuseFile, status := state.fileSystem.Open(header, input);
if status != OK {
return nil, status
}
out := new(OpenOut)
out.Fh = resp.fh
return out, OK
}
func open(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in := ing.(*OpenIn)
fmt.Printf("FUSE_OPEN: %v\n", in)
resp := c.open(h.NodeId)
if resp.status != OK {
return nil, resp.status
if fuseFile == nil {
fmt.Println("fuseFile should not be nil.")
}
out := new(OpenOut)
out.Fh = resp.fh
return out, OK
}
func readDir(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in := ing.(*ReadIn)
fmt.Printf("FUSE_READDIR: %v\n", in)
resp := c.getDirReader(h.NodeId, in.Fh)
if resp.status != OK {
return nil, resp.status
}
dirRespChan := make(chan *dirResponse, 1)
fmt.Printf("Sending dir request, in.Offset: %v\n", in.Offset)
resp.dirReq <- &dirRequest{false, h.NodeId, in.Offset, dirRespChan}
fmt.Printf("receiving dir response\n")
dirResp := <-dirRespChan
fmt.Printf("received %v\n", dirResp)
if dirResp.status != OK {
return nil, dirResp.status
}
if dirResp.entries == nil {
return nil, OK
}
buf := new(bytes.Buffer)
off := in.Offset
for _, entry := range dirResp.entries {
off++
dirent := new(Dirent)
dirent.Off = off
dirent.Ino = entry.nodeId
dirent.NameLen = uint32(len(entry.name))
dirent.Typ = (entry.mode & 0170000) >> 12
err := binary.Write(buf, binary.LittleEndian, dirent)
if err != nil {
panic("Serialization of Dirent failed")
}
buf.Write([]byte(entry.name))
buf.WriteByte(0)
n := (len(entry.name) + 1) % 8 // padding
if n != 0 {
buf.Write(make([]byte, 8-n))
}
}
out := buf.Bytes()
return out, OK
out.Fh = state.RegisterFile(fuseFile)
out.OpenFlags = flags
return out, status
}
func read(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in := ing.(*ReadIn)
fmt.Printf("FUSE_READ: %v\n", in)
resp := c.getFileReader(h.NodeId, in.Fh)
if resp.status != OK {
return nil, resp.status
}
fileRespChan := make(chan *fileResponse, 1)
fmt.Printf("Sending file request, in.Offset: %v\n", in.Offset)
resp.fileReq <- &fileRequest{h.NodeId, in.Offset, in.Size, fileRespChan}
fmt.Printf("receiving file response\n")
fileResp := <-fileRespChan
fmt.Printf("received %v\n", fileResp)
if fileResp.status != OK {
return nil, fileResp.status
}
return fileResp.data, OK
}
func flush(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in := ing.(*FlushIn)
fmt.Printf("FUSE_FLUSH: %v\n", in)
return nil, OK
}
func lookup(h *InHeader, r *bytes.Buffer, c *managerClient) (interface{}, Status) {
filename := strings.TrimRight(string(r.Bytes()), "\x00")
fmt.Printf("filename: %s\n", filename)
resp := c.lookup(h.NodeId, filename)
if resp.status != OK {
return nil, resp.status
func doCreate(state *MountState, header *InHeader, input *CreateIn, name string) (genericOut Empty, code Status) {
flags, fuseFile, entry, status := state.fileSystem.Create(header, input, name);
if status != OK {
return nil, status
}
out := new(EntryOut)
out.NodeId = resp.nodeId
out.Attr = resp.attr
out.AttrValid = 60
out.EntryValid = 60
return out, OK
}
func releaseDir(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in := ing.(*ReleaseIn)
fmt.Printf("FUSE_RELEASEDIR: %v\n", in)
resp := c.closeDir(h.NodeId, in.Fh)
if resp.status != OK {
return nil, resp.status
if fuseFile == nil {
fmt.Println("fuseFile should not be nil.")
}
return nil, OK
out := new(CreateOut)
out.Entry = *entry
out.Open.Fh = state.RegisterFile(fuseFile)
out.Open.OpenFlags = flags
return out, status
}
func release(fs FileSystem, h *InHeader, ing interface{}, c *managerClient) (interface{}, Status) {
in := ing.(*ReleaseIn)
fmt.Printf("FUSE_RELEASE: %v\n", in)
resp := c.closeFile(h.NodeId, in.Fh)
if resp.status != OK {
return nil, resp.status
}
func doRelease(state *MountState, header *InHeader, input *ReleaseIn) (out Empty, code Status) {
state.FindFile(input.Fh).Release()
state.UnregisterFile(input.Fh)
return nil, OK
}
func writer(f *os.File, in chan [][]byte, errors chan os.Error) {
fd := f.Fd()
for packet := range in {
fmt.Printf("writer, packet: %v\n", packet)
_, err := Writev(fd, packet)
if err != nil {
errors <- os.NewError(fmt.Sprintf("writer: Writev failed, err: %v", err))
continue
}
fmt.Printf("writer: OK\n")
}
}
type FileOp int
const (
openDirOp = FileOp(1)
getHandleOp = FileOp(2)
closeDirOp = FileOp(3)
lookupOp = FileOp(4)
getPathOp = FileOp(5)
openOp = FileOp(6)
getFileHandleOp = FileOp(7)
closeFileOp = FileOp(8)
)
type managerRequest struct {
nodeId uint64
fh uint64
op FileOp
resp chan *managerResponse
filename string
}
type managerResponse struct {
nodeId uint64
fh uint64
dirReq chan *dirRequest
fileReq chan *fileRequest
status Status
attr Attr
path string
}
type dirEntry struct {
nodeId uint64
name string
mode uint32
}
type dirRequest struct {
isClose bool
nodeId uint64
offset uint64
resp chan *dirResponse
}
type dirResponse struct {
entries []*dirEntry
status Status
}
type dirHandle struct {
fh uint64
nodeId uint64
req chan *dirRequest
}
type fileRequest struct {
nodeId uint64
offset uint64
size uint32
resp chan *fileResponse
}
type fileResponse struct {
data []byte
status Status
func doRead(state *MountState, header *InHeader, input *ReadIn) (out Empty, code Status) {
output, code := state.FindFile(input.Fh).Read(input)
return output, code
}
type fileHandle struct {
fh uint64
nodeId uint64
file File
req chan *fileRequest
func doWrite(state *MountState, header *InHeader, input *WriteIn, data []byte) (out WriteOut, code Status) {
n, status := state.FindFile(input.Fh).Write(input, data)
out.Size = n
return out, status
}
type manager struct {
fs FileSystem
client *managerClient
dirHandles map[uint64]*dirHandle
fileHandles map[uint64]*fileHandle
cnt uint64
nodes map[uint64]string
nodesByPath map[string]uint64
nodeMax uint64
func doFsync(state *MountState, header *InHeader, input *FsyncIn) (code Status) {
return state.FindFile(input.Fh).Fsync(input)
}
func startManager(fs FileSystem, requests chan *managerRequest) {
m := new(manager)
m.fs = fs
m.client = &managerClient{requests}
m.dirHandles = make(map[uint64]*dirHandle)
m.fileHandles = make(map[uint64]*fileHandle)
m.nodes = make(map[uint64]string)
m.nodes[0] = ""
m.nodes[1] = "" // Root
m.nodeMax = 1
m.nodesByPath = make(map[string]uint64)
m.nodesByPath[""] = 1
go m.run(requests)
func doFlush(state *MountState, header *InHeader, input *FlushIn) (out Empty, code Status) {
return nil, state.FindFile(input.Fh).Flush()
}
type managerClient struct {
requests chan *managerRequest
func doSetattr(state *MountState, header *InHeader, input *SetAttrIn) (out *AttrOut, code Status) {
// TODO - if Fh != 0, we should do a FSetAttr instead.
return state.fileSystem.SetAttr(header, input)
}
func (c *managerClient) makeManagerRequest(nodeId uint64, fh uint64, op FileOp, filename string) (resp *managerResponse) {
fmt.Printf("makeManagerRequest, nodeId = %d, fh = %d, op = %d, filename = %s\n", nodeId, fh, op, filename)
req := &managerRequest{nodeId, fh, op, make(chan *managerResponse, 1), filename}
c.requests <- req
resp = <-req.resp
fmt.Printf("makeManagerRequest, resp: %v\n", resp)
return
}
func (c *managerClient) lookup(nodeId uint64, filename string) (resp *managerResponse) {
return c.makeManagerRequest(nodeId, 0, lookupOp, filename)
}
func (c *managerClient) openDir(nodeId uint64) (resp *managerResponse) {
return c.makeManagerRequest(nodeId, 0, openDirOp, "")
}
func (c *managerClient) open(nodeId uint64) (resp *managerResponse) {
return c.makeManagerRequest(nodeId, 0, openOp, "")
}
func (c *managerClient) getDirReader(nodeId, fh uint64) (resp *managerResponse) {
return c.makeManagerRequest(nodeId, fh, getHandleOp, "")
}
func (c *managerClient) getFileReader(nodeId, fh uint64) (resp *managerResponse) {
return c.makeManagerRequest(nodeId, fh, getFileHandleOp, "")
}
func (c *managerClient) getPath(nodeId uint64) (resp *managerResponse) {
return c.makeManagerRequest(nodeId, 0, getPathOp, "")
}
////////////////////////////////////////////////////////////////
// Handling directories
func (c *managerClient) closeDir(nodeId, fh uint64) (resp *managerResponse) {
return c.makeManagerRequest(nodeId, fh, closeDirOp, "")
}
func (c *managerClient) closeFile(nodeId, fh uint64) (resp *managerResponse) {
return c.makeManagerRequest(nodeId, fh, closeFileOp, "")
func doReleaseDir(state *MountState, header *InHeader, input *ReleaseIn) (out Empty, code Status) {
state.FindDir(input.Fh).ReleaseDir()
state.UnregisterDir(input.Fh)
return nil, OK
}
func (m *manager) run(requests chan *managerRequest) {
var resp *managerResponse
for req := range requests {
switch req.op {
case openDirOp:
resp = m.openDir(req)
case getHandleOp:
resp = m.getHandle(req)
case closeDirOp:
resp = m.closeDir(req)
case lookupOp:
resp = m.lookup(req)
case getPathOp:
resp = m.getPath(req)
case openOp:
resp = m.open(req)
case getFileHandleOp:
resp = m.getFileHandle(req)
case closeFileOp:
resp = m.closeFile(req)
default:
panic(fmt.Sprintf("Unknown FileOp: %v", req.op))
}
req.resp <- resp
func doOpenDir(state *MountState, header *InHeader, input *OpenIn) (genericOut Empty, code Status) {
flags, fuseDir, status := state.fileSystem.OpenDir(header, input);
if status != OK {
return nil, status
}
}
func (m *manager) openDir(req *managerRequest) (resp *managerResponse) {
resp = new(managerResponse)
m.cnt++
h := new(dirHandle)
h.fh = m.cnt
h.nodeId = req.nodeId
h.req = make(chan *dirRequest, 1)
m.dirHandles[h.fh] = h
dir, ok := m.nodes[req.nodeId]
if !ok {
resp.status = ENOENT
return
}
go readDirRoutine(dir, m.fs, m.client, h.req)
resp.fh = h.fh
return
out := new(OpenOut)
out.Fh = state.RegisterDir(fuseDir)
out.OpenFlags = flags
return out, status
}
func (m *manager) open(req *managerRequest) (resp *managerResponse) {
resp = new(managerResponse)
path, ok := m.nodes[req.nodeId]
if !ok {
resp.status = ENOENT
return
}
var file File
file, resp.status = m.fs.Open(path)
if resp.status != OK {
return
}
m.cnt++
h := new(fileHandle)
h.fh = m.cnt
h.nodeId = req.nodeId
h.file = file
h.req = make(chan *fileRequest, 1)
m.fileHandles[h.fh] = h
go readFileRoutine(m.fs, m.client, h)
resp.fh = h.fh
return
}
func (m *manager) getHandle(req *managerRequest) (resp *managerResponse) {
fmt.Printf("getHandle, fh: %v\n", req.fh)
resp = new(managerResponse)
h, ok := m.dirHandles[req.fh]
if !ok {
resp.status = ENOENT
return
func doReadDir(state *MountState, header *InHeader, input *ReadIn) (out Empty, code Status) {
dir := state.FindDir(input.Fh)
entries, code := dir.ReadDir(input)
if entries == nil {
var emptyBytes []byte
return emptyBytes, code
}
fmt.Printf("Handle found\n")
resp.dirReq = h.req
return
return entries.Bytes(), code
}
func (m *manager) getFileHandle(req *managerRequest) (resp *managerResponse) {
fmt.Printf("getFileHandle, fh: %v\n", req.fh)
resp = new(managerResponse)
h, ok := m.fileHandles[req.fh]
if !ok {
resp.status = ENOENT
return
}
fmt.Printf("File handle found\n")
resp.fileReq = h.req
return
func doFsyncDir(state *MountState, header *InHeader, input *FsyncIn) (code Status) {
return state.FindDir(input.Fh).FsyncDir(input)
}
func (m *manager) closeDir(req *managerRequest) (resp *managerResponse) {
resp = new(managerResponse)
h, ok := m.dirHandles[req.fh]
if !ok {
resp.status = ENOENT
return
}
m.dirHandles[h.fh] = nil, false
close(h.req)
return
////////////////////////////////////////////////////////////////
// DentryList.
func (de *DEntryList) AddString(name string, inode uint64, mode uint32) {
de.Add([]byte(name), inode, mode)
}
func (m *manager) closeFile(req *managerRequest) (resp *managerResponse) {
resp = new(managerResponse)
h, ok := m.fileHandles[req.fh]
if !ok {
resp.status = ENOENT
return
}
file := h.file
m.fileHandles[h.fh] = nil, false
close(h.req)
resp.status = file.Close()
return
}
func (m *manager) lookup(req *managerRequest) (resp *managerResponse) {
resp = new(managerResponse)
parent, ok := m.nodes[req.nodeId]
if !ok {
resp.status = ENOENT
return
}
attr, status := m.fs.GetAttr(path.Join(parent, req.filename))
if status != OK {
resp.status = status
}
resp.attr = attr
fullPath := path.Clean(path.Join(parent, req.filename))
nodeId, ok := m.nodesByPath[fullPath]
if !ok {
m.nodeMax++
nodeId = m.nodeMax
m.nodes[nodeId] = fullPath
m.nodesByPath[fullPath] = nodeId
}
func (de *DEntryList) Add(name []byte, inode uint64, mode uint32) {
de.offset++
resp.nodeId = nodeId
return
}
dirent := new(Dirent)
dirent.Off = de.offset
dirent.Ino = inode
dirent.NameLen = uint32(len(name))
dirent.Typ = (mode & 0170000) >> 12
func (m *manager) getPath(req *managerRequest) (resp *managerResponse) {
resp = new(managerResponse)
path, ok := m.nodes[req.nodeId]
if !ok {
resp.status = ENOENT
return
err := binary.Write(&de.buf, binary.LittleEndian, dirent)
if err != nil {
panic("Serialization of Dirent failed")
}
resp.path = path
return
}
func readDirRoutine(dir string, fs FileSystem, c *managerClient, requests chan *dirRequest) {
defer close(requests)
dir = path.Clean(dir)
names, status := fs.List(dir)
i := uint64(0)
for req := range requests {
if status != OK {
req.resp <- &dirResponse{nil, status}
return
}
if req.offset != i {
fmt.Printf("readDirRoutine: i = %v, changing offset to %v\n", i, req.offset)
i = req.offset
}
if req.isClose {
return
}
if i < uint64(len(names)) {
entry := new(dirEntry)
entry.name = names[i]
lookupResp := c.lookup(req.nodeId, entry.name)
if lookupResp.status != OK {
req.resp <- &dirResponse{nil, lookupResp.status}
return
}
entry.nodeId = lookupResp.nodeId
entry.mode = lookupResp.attr.Mode
req.resp <- &dirResponse{[]*dirEntry{entry}, OK}
i++
} else {
req.resp <- &dirResponse{nil, OK}
}
de.buf.Write([]byte(name))
de.buf.WriteByte(0)
n := (len(name) + 1) % 8 // padding
if n != 0 {
de.buf.Write(make([]byte, 8-n))
}
}
func readFileRoutine(fs FileSystem, c *managerClient, h *fileHandle) {
defer close(h.req)
offset := uint64(0)
for req := range h.req {
data := make([]byte, req.size)
n, err := h.file.ReadAt(data, int64(offset))
if err != nil {
req.resp <- &fileResponse{nil, EIO}
continue
}
req.resp <- &fileResponse{data[0:n], OK}
}
func (de *DEntryList) Bytes() []byte {
return de.buf.Bytes()
}
package fuse
import (
"fmt"
"log"
"os"
"path"
"rand"
"strings"
"testing"
"time"
)
var (
testFileNames = []string{"one", "two", "three.txt"}
)
type testFuse struct{}
func (fs *testFuse) GetAttr(path string) (out Attr, code Status) {
if strings.HasSuffix(path, ".txt") {
out.Mode = S_IFREG + 0644
out.Size = 13
} else {
out.Mode = S_IFDIR + 0755
}
out.Mtime = uint64(time.Seconds())
return
}
func (fs *testFuse) List(dir string) (names []string, code Status) {
names = testFileNames
return
}
func (fs *testFuse) Open(path string) (file File, code Status) {
file = &testFile{}
return
}
type testFile struct{}
func (f *testFile) ReadAt(data []byte, offset int64) (n int, err os.Error) {
if offset < 13 {
our := []byte("Hello world!\n")[offset:]
for i, b := range our {
data[i] = b
}
n = len(our)
return
}
return 0, os.EOF
}
func (f *testFile) Close() (status Status) {
return OK
}
func errorHandler(errors chan os.Error) {
for err := range errors {
log.Println("MountPoint.errorHandler: ", err)
if err == os.EOF {
break
}
}
}
func TestMount(t *testing.T) {
fs := new(testFuse)
tempMountDir := MakeTempDir()
fmt.Println("Tmpdir is: ", tempMountDir)
defer os.Remove(tempMountDir)
m, err, errors := Mount(tempMountDir, fs)
if err != nil {
t.Fatalf("Can't mount a dir, err: %v", err)
}
defer func() {
err := m.Unmount()
if err != nil {
t.Fatalf("Can't unmount a dir, err: %v", err)
}
}()
// Question: how to neatly do error handling?
go errorHandler(errors)
f, err := os.Open(tempMountDir, os.O_RDONLY, 0)
if err != nil {
t.Fatalf("Can't open a dir: %s, err: %v", tempMountDir, err)
}
defer f.Close()
names, err := f.Readdirnames(10)
if err != nil {
t.Fatalf("Can't ls a dir: %s, err: %v", tempMountDir, err)
}
has := strings.Join(names, ", ")
wanted := strings.Join(testFileNames, ", ")
if has != wanted {
t.Errorf("Ls returned wrong results, has: [%s], wanted: [%s]", has, wanted)
return
}
}
// Make a temporary directory securely.
func MakeTempDir() string {
source := rand.NewSource(time.Nanoseconds())
number := source.Int63() & 0xffff
name := fmt.Sprintf("tmp%d", number)
fullName := path.Join(os.TempDir(), name)
err := os.Mkdir(fullName, 0700)
if err != nil {
panic("Mkdir() should always succeed: " + fullName)
}
return fullName
}
// Random odds and ends.
package fuse
import (
"bytes"
"encoding/binary"
"rand"
"os"
"time"
"fmt"
"path"
)
// Make a temporary directory securely.
//
// Should move somewhere into Go library?
func MakeTempDir() string {
source := rand.NewSource(time.Nanoseconds())
number := source.Int63() & 0xffff
name := fmt.Sprintf("tmp%d", number)
fullName := path.Join(os.TempDir(), name)
err := os.Mkdir(fullName, 0700)
if err != nil {
panic("Mkdir() should always succeed: " + fullName)
}
return fullName
}
// Convert os.Error back to Errno based errors.
func OsErrorToFuseError(err os.Error) Status {
if err != nil {
asErrno, ok := err.(os.Errno)
if ok {
return Status(asErrno)
}
asSyscallErr, ok := err.(*os.SyscallError)
if ok {
return Status(asSyscallErr.Errno)
}
// Should not happen. Should we log an error somewhere?
return ENOSYS
}
return OK
}
func operationName(opcode uint32) string {
switch opcode {
case FUSE_LOOKUP:
return "FUSE_LOOKUP"
case FUSE_FORGET:
return "FUSE_FORGET"
case FUSE_GETATTR:
return "FUSE_GETATTR"
case FUSE_SETATTR:
return "FUSE_SETATTR"
case FUSE_READLINK:
return "FUSE_READLINK"
case FUSE_SYMLINK:
return "FUSE_SYMLINK"
case FUSE_MKNOD:
return "FUSE_MKNOD"
case FUSE_MKDIR:
return "FUSE_MKDIR"
case FUSE_UNLINK:
return "FUSE_UNLINK"
case FUSE_RMDIR:
return "FUSE_RMDIR"
case FUSE_RENAME:
return "FUSE_RENAME"
case FUSE_LINK:
return "FUSE_LINK"
case FUSE_OPEN:
return "FUSE_OPEN"
case FUSE_READ:
return "FUSE_READ"
case FUSE_WRITE:
return "FUSE_WRITE"
case FUSE_STATFS:
return "FUSE_STATFS"
case FUSE_RELEASE:
return "FUSE_RELEASE"
case FUSE_FSYNC:
return "FUSE_FSYNC"
case FUSE_SETXATTR:
return "FUSE_SETXATTR"
case FUSE_GETXATTR:
return "FUSE_GETXATTR"
case FUSE_LISTXATTR:
return "FUSE_LISTXATTR"
case FUSE_REMOVEXATTR:
return "FUSE_REMOVEXATTR"
case FUSE_FLUSH:
return "FUSE_FLUSH"
case FUSE_INIT:
return "FUSE_INIT"
case FUSE_OPENDIR:
return "FUSE_OPENDIR"
case FUSE_READDIR:
return "FUSE_READDIR"
case FUSE_RELEASEDIR:
return "FUSE_RELEASEDIR"
case FUSE_FSYNCDIR:
return "FUSE_FSYNCDIR"
case FUSE_GETLK:
return "FUSE_GETLK"
case FUSE_SETLK:
return "FUSE_SETLK"
case FUSE_SETLKW:
return "FUSE_SETLKW"
case FUSE_ACCESS:
return "FUSE_ACCESS"
case FUSE_CREATE:
return "FUSE_CREATE"
case FUSE_INTERRUPT:
return "FUSE_INTERRUPT"
case FUSE_BMAP:
return "FUSE_BMAP"
case FUSE_DESTROY:
return "FUSE_DESTROY"
case FUSE_IOCTL:
return "FUSE_IOCTL"
case FUSE_POLL:
return "FUSE_POLL"
}
return "UNKNOWN"
}
func errorString(code Status) string {
if code == OK {
return "OK"
}
return fmt.Sprintf("%d=%v", code, os.Errno(code))
}
func newInput(opcode uint32) Empty {
switch opcode {
case FUSE_FORGET:
return new(ForgetIn)
case FUSE_GETATTR:
return new(GetAttrIn)
case FUSE_MKNOD:
return new(MknodIn)
case FUSE_MKDIR:
return new(MkdirIn)
case FUSE_RENAME:
return new(RenameIn)
case FUSE_LINK:
return new(LinkIn)
case FUSE_SETATTR:
return new(SetAttrIn)
case FUSE_OPEN:
return new(OpenIn)
case FUSE_CREATE:
return new(CreateIn)
case FUSE_FLUSH:
return new(FlushIn)
case FUSE_RELEASE:
return new(ReleaseIn)
case FUSE_READ:
return new(ReadIn)
case FUSE_WRITE:
return new(WriteIn)
case FUSE_FSYNC:
return new(FsyncIn)
// case FUSE_GET/SETLK(W)
case FUSE_ACCESS:
return new(AccessIn)
case FUSE_INIT:
return new(InitIn)
case FUSE_BMAP:
return new(BmapIn)
case FUSE_INTERRUPT:
return new(InterruptIn)
case FUSE_IOCTL:
return new(IoctlIn)
case FUSE_POLL:
return new(PollIn)
case FUSE_SETXATTR:
return new(SetXAttrIn)
case FUSE_GETXATTR:
return new(GetXAttrIn)
case FUSE_OPENDIR:
return new(OpenIn)
case FUSE_FSYNCDIR:
return new(FsyncIn)
case FUSE_READDIR:
return new(ReadIn)
case FUSE_RELEASEDIR:
return new(ReleaseIn)
}
return nil
}
func parseLittleEndian(b *bytes.Buffer, data interface{}) bool {
err := binary.Read(b, binary.LittleEndian, data)
if err == nil {
return true
}
if err == os.EOF {
return false
}
panic(fmt.Sprintf("Cannot parse %v", data))
}
package fuse
import (
"os"
"testing"
"syscall"
)
func TestOsErrorToFuseError(t *testing.T) {
errNo := OsErrorToFuseError(os.EPERM)
if errNo != syscall.EPERM {
t.Errorf("Wrong conversion %v != %v", errNo, syscall.EPERM)
}
e := os.NewSyscallError("syscall", syscall.EPERM)
errNo = OsErrorToFuseError(e)
if errNo != syscall.EPERM {
t.Errorf("Wrong conversion %v != %v", errNo, syscall.EPERM)
}
}
......@@ -9,9 +9,6 @@ import (
"unsafe"
)
// Make a type to attach the Unmount method.
type mounted string
func Socketpair(network string) (l, r *os.File, err os.Error) {
var domain int
var typ int
......@@ -36,7 +33,7 @@ func Socketpair(network string) (l, r *os.File, err os.Error) {
// Mount create a fuse fs on the specified mount point. The returned
// mount point is always absolute.
func mount(mountPoint string) (f *os.File, m mounted, err os.Error) {
func mount(mountPoint string) (f *os.File, finalMountPoint string, err os.Error) {
local, remote, err := Socketpair("unixgram")
if err != nil {
return
......@@ -71,12 +68,11 @@ func mount(mountPoint string) (f *os.File, m mounted, err os.Error) {
}
f, err = getFuseConn(local)
m = mounted(mountPoint)
finalMountPoint = mountPoint
return
}
func (m mounted) Unmount() (err os.Error) {
mountPoint := string(m)
func unmount(mountPoint string) (err os.Error) {
dir, _ := path.Split(mountPoint)
pid, err := os.ForkExec("/bin/fusermount",
[]string{"/bin/fusermount", "-u", mountPoint},
......@@ -131,7 +127,7 @@ func getFuseConn(local *os.File) (f *os.File, err os.Error) {
// n, oobn, recvflags - todo: error checking.
_, oobn, _,
errno := syscall.Recvmsg(
local.Fd(), data[:], control[:], nil, 0)
local.Fd(), data[:], control[:], 0)
if errno != 0 {
return
}
......@@ -151,6 +147,6 @@ func getFuseConn(local *os.File) (f *os.File, err os.Error) {
err = os.NewError(fmt.Sprintf("getFuseConn: fd < 0: %d", fd))
return
}
f = os.NewFile(int(fd), "fuse-conn")
f = os.NewFile(int(fd), "<fuseConnection>")
return
}
// A FUSE filesystem that shunts all request to an underlying file
// system. Its main purpose is to provide test coverage without
// having to build an actual synthetic filesystem.
package fuse
import (
"fmt"
"os"
"path"
"syscall"
)
var _ = fmt.Println
type PassThroughFuse struct {
root string
}
func NewPassThroughFuse(root string) (out *PassThroughFuse) {
out = new(PassThroughFuse)
out.root = root
return out
}
func CopyFileInfo(fi *os.FileInfo, attr *Attr) {
attr.Ino = uint64(fi.Ino)
attr.Size = uint64(fi.Size)
attr.Blocks = uint64(fi.Blocks)
attr.Atime = uint64(fi.Atime_ns / 1e9)
attr.Atimensec = uint32(fi.Atime_ns % 1e9)
attr.Mtime = uint64(fi.Mtime_ns / 1e9)
attr.Mtimensec = uint32(fi.Mtime_ns % 1e9)
attr.Ctime = uint64(fi.Ctime_ns / 1e9)
attr.Ctimensec = uint32(fi.Ctime_ns % 1e9)
attr.Mode = fi.Mode
attr.Nlink = uint32(fi.Nlink)
attr.Uid = uint32(fi.Uid)
attr.Gid = uint32(fi.Gid)
attr.Rdev = uint32(fi.Rdev)
attr.Blksize = uint32(fi.Blksize)
}
func (self *PassThroughFuse) Init() (*InitOut, Status) {
return new(InitOut), OK
}
func (self *PassThroughFuse) Destroy() {
}
func (self *PassThroughFuse) GetPath(relPath string) string {
return path.Join(self.root, relPath)
}
func (self *PassThroughFuse) GetAttr(name string) (*Attr, Status) {
fullPath := self.GetPath(name)
fi, err := os.Lstat(fullPath)
if err != nil {
return nil, ENOENT
}
out := new(Attr)
CopyFileInfo(fi, out)
return out, OK
}
func (self *PassThroughFuse) OpenDir(name string) (fuseFile RawFuseDir, status Status) {
// What other ways beyond O_RDONLY are there to open
// directories?
f, err := os.Open(self.GetPath(name), os.O_RDONLY, 0)
if err != nil {
return nil, OsErrorToFuseError(err)
}
return &PassThroughFile{file: f}, OK
}
func (self *PassThroughFuse) Open(name string, flags uint32) (fuseFile RawFuseFile, status Status) {
f, err := os.Open(self.GetPath(name), int(flags), 0)
if err != nil {
return nil, OsErrorToFuseError(err)
}
return &PassThroughFile{file: f}, OK
}
func (self *PassThroughFuse) Chmod(path string, mode uint32) (code Status) {
err := os.Chmod(self.GetPath(path), mode)
return OsErrorToFuseError(err)
}
func (self *PassThroughFuse) Chown(path string, uid uint32, gid uint32) (code Status) {
return OsErrorToFuseError(os.Chown(self.GetPath(path), int(uid), int(gid)))
}
func (self *PassThroughFuse) Truncate(path string, offset uint64) (code Status) {
return OsErrorToFuseError(os.Truncate(self.GetPath(path), int64(offset)))
}
func (self *PassThroughFuse) Utimens(path string, AtimeNs uint64, MtimeNs uint64) (code Status) {
return OsErrorToFuseError(os.Chtimes(self.GetPath(path), int64(AtimeNs), int64(MtimeNs)))
}
func (self *PassThroughFuse) Readlink(name string) (out string, code Status) {
f, err := os.Readlink(self.GetPath(name))
return f, OsErrorToFuseError(err)
}
func (self *PassThroughFuse) Mknod(name string, mode uint32, dev uint32) (code Status) {
return Status(syscall.Mknod(self.GetPath(name), mode, int(dev)))
}
func (self *PassThroughFuse) Mkdir(path string, mode uint32) (code Status) {
return OsErrorToFuseError(os.Mkdir(self.GetPath(path), mode))
}
func (self *PassThroughFuse) Unlink(name string) (code Status) {
return OsErrorToFuseError(os.Remove(self.GetPath(name)))
}
func (self *PassThroughFuse) Rmdir(name string) (code Status) {
return OsErrorToFuseError(os.Remove(self.GetPath(name)))
}
func (self *PassThroughFuse) Symlink(pointedTo string, linkName string) (code Status) {
return OsErrorToFuseError(os.Symlink(pointedTo, self.GetPath(linkName)))
}
func (self *PassThroughFuse) Rename(oldPath string, newPath string) (code Status) {
err := os.Rename(self.GetPath(oldPath), self.GetPath(newPath))
return OsErrorToFuseError(err)
}
func (self *PassThroughFuse) Link(orig string, newName string) (code Status) {
return OsErrorToFuseError(os.Link(self.GetPath(orig), self.GetPath(newName)))
}
func (self *PassThroughFuse) Access(name string, mode uint32) (code Status) {
return Status(syscall.Access(self.GetPath(name), mode))
}
func (self *PassThroughFuse) Create(path string, flags uint32, mode uint32) (fuseFile RawFuseFile, code Status) {
f, err := os.Open(self.GetPath(path), int(flags)|os.O_CREAT, mode)
return &PassThroughFile{file: f}, OsErrorToFuseError(err)
}
////////////////////////////////////////////////////////////////
type PassThroughFile struct {
file *os.File
}
func (self *PassThroughFile) Read(input *ReadIn) ([]byte, Status) {
buf := make([]byte, input.Size)
slice := buf[:]
n, err := self.file.ReadAt(slice, int64(input.Offset))
if err == os.EOF {
// TODO - how to signal EOF?
return slice[:n], OK
}
return slice[:n], OsErrorToFuseError(err)
}
func (self *PassThroughFile) Write(input *WriteIn, data []byte) (uint32, Status) {
n, err := self.file.WriteAt(data, int64(input.Offset))
return uint32(n), OsErrorToFuseError(err)
}
func (self *PassThroughFile) Flush() Status {
return OK
}
func (self *PassThroughFile) Release() {
self.file.Close()
}
func (self *PassThroughFile) Fsync(*FsyncIn) (code Status) {
return Status(syscall.Fsync(self.file.Fd()))
}
func (self *PassThroughFile) ReadDir(input *ReadIn) (*DEntryList, Status) {
list := new(DEntryList)
// TODO - should we try to accomodate the requested Size?
// (typically: 4096.)
fis, err := self.file.Readdir(-1)
if err != nil {
return nil, OsErrorToFuseError(err)
}
for _, val := range fis {
list.AddString(val.Name, val.Ino, val.Mode)
}
return list, OK
}
func (self *PassThroughFile) ReleaseDir() {
self.file.Close()
}
func (self *PassThroughFile) FsyncDir(input *FsyncIn) (code Status) {
return ENOSYS
}
package fuse
import (
"fmt"
"log"
"os"
"path"
"strings"
"testing"
"syscall"
)
var _ = strings.Join
var _ = log.Println
////////////////
func IsDir(name string) bool {
fi, _ := os.Lstat(name)
return fi != nil && fi.IsDirectory()
}
func IsFile(name string) bool {
fi, _ := os.Lstat(name)
return fi != nil && fi.IsRegular()
}
func FileExists(name string) bool {
_, err := os.Lstat(name)
return err == nil
}
////////////////
// state for our testcase, mostly constants
const contents string = "ABC"
const mode uint32 = 0757
type testCase struct {
origDir string
mountPoint string
mountFile string
mountSubdir string
mountSubfile string
origFile string
origSubdir string
origSubfile string
tester *testing.T
state *MountState
}
// Create and mount filesystem.
func (self *testCase) Setup(t *testing.T) {
self.tester = t
const name string = "hello.txt"
const subdir string = "subdir"
self.origDir = MakeTempDir()
self.mountPoint = MakeTempDir()
self.mountFile = path.Join(self.mountPoint, name)
self.mountSubdir = path.Join(self.mountPoint, subdir)
self.mountSubfile = path.Join(self.mountSubdir, "subfile")
self.origFile = path.Join(self.origDir, name)
self.origSubdir = path.Join(self.origDir, subdir)
self.origSubfile = path.Join(self.origSubdir, "subfile")
fs := NewPathFileSystemConnector(NewPassThroughFuse(self.origDir))
self.state = NewMountState(fs)
self.state.Mount(self.mountPoint, false)
//self.state.Debug = false
self.state.Debug = true
fmt.Println("Orig ", self.origDir, " mount ", self.mountPoint)
}
// Unmount and del.
func (self *testCase) Cleanup() {
fmt.Println("Unmounting.")
err := self.state.Unmount()
if err != nil {
self.tester.Errorf("Can't unmount a dir, err: %v", err)
}
os.Remove(self.mountPoint)
os.RemoveAll(self.origDir)
}
////////////////
// Utilities.
func (self *testCase) makeOrigSubdir() {
err := os.Mkdir(self.origSubdir, 0777)
if err != nil {
self.tester.Errorf("orig mkdir subdir %v", err)
}
}
func (self *testCase) removeMountSubdir() {
err := os.RemoveAll(self.mountSubdir)
if err != nil {
self.tester.Errorf("orig rmdir subdir %v", err)
}
}
func (self *testCase) removeMountFile() {
os.Remove(self.mountFile)
// ignore errors.
}
func (self *testCase) writeOrigFile() {
f, err := os.Open(self.origFile, os.O_WRONLY|os.O_CREAT, 0700)
if err != nil {
self.tester.Errorf("Error orig open: %v", err)
}
_, err = f.Write([]byte(contents))
if err != nil {
self.tester.Errorf("Write %v", err)
}
f.Close()
}
////////////////
// Tests.
func (self *testCase) testOpenUnreadable() {
_, err := os.Open(path.Join(self.mountPoint, "doesnotexist"), os.O_RDONLY, 0)
if err == nil {
self.tester.Errorf("open non-existent should raise error")
}
}
func (self *testCase) testReadThroughFuse() {
self.writeOrigFile()
fmt.Println("Testing chmod.")
err := os.Chmod(self.mountFile, mode)
if err != nil {
self.tester.Errorf("Chmod %v", err)
}
fmt.Println("Testing Lstat.")
fi, err := os.Lstat(self.mountFile)
if err != nil {
self.tester.Errorf("Lstat %v", err)
}
if (fi.Mode & 0777) != mode {
self.tester.Errorf("Wrong mode %o != %o", fi.Mode, mode)
}
// Open (for read), read.
fmt.Println("Testing open.")
f, err := os.Open(self.mountFile, os.O_RDONLY, 0)
if err != nil {
self.tester.Errorf("Fuse open %v", err)
}
fmt.Println("Testing read.")
var buf [1024]byte
slice := buf[:]
n, err := f.Read(slice)
if len(slice[:n]) != len(contents) {
self.tester.Errorf("Content error %v", slice)
}
fmt.Println("Testing close.")
f.Close()
self.removeMountFile()
}
func (self *testCase) testRemove() {
self.writeOrigFile()
fmt.Println("Testing remove.")
err := os.Remove(self.mountFile)
if err != nil {
self.tester.Errorf("Remove %v", err)
}
_, err = os.Lstat(self.origFile)
if err == nil {
self.tester.Errorf("Lstat() after delete should have generated error.")
}
}
func (self *testCase) testWriteThroughFuse() {
// Create (for write), write.
fmt.Println("Testing create.")
f, err := os.Open(self.mountFile, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
self.tester.Errorf("Fuse create/open %v", err)
}
fmt.Println("Testing write.")
n, err := f.WriteString(contents)
if err != nil {
self.tester.Errorf("fuse write %v", err)
}
if n != len(contents) {
self.tester.Errorf("Write mismatch: %v of %v", n, len(contents))
}
fi, err := os.Lstat(self.origFile)
if fi.Mode&0777 != 0644 {
self.tester.Errorf("create mode error %o", fi.Mode&0777)
}
f, err = os.Open(self.origFile, os.O_RDONLY, 0)
if err != nil {
self.tester.Errorf("orig open %v", err)
}
var buf [1024]byte
slice := buf[:]
n, err = f.Read(slice)
if err != nil {
self.tester.Errorf("orig read %v", err)
}
fmt.Println("Orig contents", slice[:n])
if string(slice[:n]) != contents {
self.tester.Errorf("write contents error %v", slice[:n])
}
f.Close()
self.removeMountFile()
}
func (self *testCase) testMkdirRmdir() {
// Mkdir/Rmdir.
err := os.Mkdir(self.mountSubdir, 0777)
if err != nil {
self.tester.Errorf("mount mkdir", err)
}
fi, err := os.Lstat(self.origSubdir)
if !fi.IsDirectory() {
self.tester.Errorf("Not a directory: %o", fi.Mode)
}
err = os.Remove(self.mountSubdir)
if err != nil {
self.tester.Errorf("rmdir %v", err)
}
}
func (self *testCase) testLink() {
fmt.Println("Testing hard links.")
self.writeOrigFile()
err := os.Mkdir(self.origSubdir, 0777)
if err != nil {
self.tester.Errorf("mount mkdir", err)
}
// Link.
err = os.Link(self.mountFile, self.mountSubfile)
if err != nil {
self.tester.Errorf("mount link %v", err)
}
fi, err := os.Lstat(self.mountFile)
if fi.Nlink != 2 {
self.tester.Errorf("Expect 2 links: %v", fi)
}
f, err := os.Open(self.mountSubfile, os.O_RDONLY, 0)
var buf [1024]byte
slice := buf[:]
n, err := f.Read(slice)
f.Close()
strContents := string(slice[:n])
if strContents != contents {
self.tester.Errorf("Content error: %v", slice[:n])
}
self.removeMountSubdir()
self.removeMountFile()
}
func (self *testCase) testSymlink() {
fmt.Println("testing symlink/readlink.")
self.writeOrigFile()
linkFile := "symlink-file"
orig := "hello.txt"
err := os.Symlink(orig, path.Join(self.mountPoint, linkFile))
defer os.Remove(path.Join(self.mountPoint, linkFile))
defer self.removeMountFile()
if err != nil {
self.tester.Errorf("symlink %v", err)
}
origLink := path.Join(self.origDir, linkFile)
fi, err := os.Lstat(origLink)
if err != nil {
self.tester.Errorf("link lstat %v", err)
return
}
if !fi.IsSymlink() {
self.tester.Errorf("not a symlink: %o", fi.Mode)
return
}
read, err := os.Readlink(path.Join(self.mountPoint, linkFile))
if err != nil {
self.tester.Errorf("orig readlink %v", err)
return
}
if read != orig {
self.tester.Errorf("unexpected symlink value '%v'", read)
}
}
func (self *testCase) testRename() {
fmt.Println("Testing rename.")
self.writeOrigFile()
self.makeOrigSubdir()
err := os.Rename(self.mountFile, self.mountSubfile)
if err != nil {
self.tester.Errorf("rename %v", err)
}
if FileExists(self.origFile) {
self.tester.Errorf("original %v still exists.", self.origFile)
}
if !FileExists(self.origSubfile) {
self.tester.Errorf("destination %v does not exist.", self.origSubfile)
}
self.removeMountSubdir()
}
func (self *testCase) testAccess() {
self.writeOrigFile()
err := os.Chmod(self.origFile, 0)
if err != nil {
self.tester.Errorf("chmod %v", err)
}
// Ugh - copied from unistd.h
const W_OK uint32 = 2
errCode := syscall.Access(self.mountFile, W_OK)
if errCode != syscall.EACCES {
self.tester.Errorf("Expected EACCES for non-writable, %v %v", errCode, syscall.EACCES)
}
err = os.Chmod(self.origFile, 0222)
if err != nil {
self.tester.Errorf("chmod %v", err)
}
errCode = syscall.Access(self.mountFile, W_OK)
if errCode != 0 {
self.tester.Errorf("Expected no error code for writable. %v", errCode)
}
self.removeMountFile()
self.removeMountFile()
}
func (self *testCase) testMknod() {
fmt.Println("Testing mknod.")
errNo := syscall.Mknod(self.mountFile, syscall.S_IFIFO|0777, 0)
if errNo != 0 {
self.tester.Errorf("Mknod %v", errNo)
}
fi, _ := os.Lstat(self.origFile)
if fi == nil || !fi.IsFifo() {
self.tester.Errorf("Expected FIFO filetype.")
}
self.removeMountFile()
}
func (self *testCase) testReaddir() {
fmt.Println("Testing rename.")
self.writeOrigFile()
self.makeOrigSubdir()
dir, err := os.Open(self.mountPoint, os.O_RDONLY, 0)
if err != nil {
self.tester.Errorf("opendir err %v", err)
return
}
infos, err := dir.Readdir(10)
if err != nil {
self.tester.Errorf("readdir err %v", err)
}
if len(infos) != 2 {
self.tester.Errorf("infos mismatch %v", infos)
} else {
if infos[0].Name != "hello.txt" || infos[1].Name != "subdir" {
self.tester.Errorf("names incorrect %v", infos)
}
}
dir.Close()
self.removeMountSubdir()
self.removeMountFile()
}
func (self *testCase) testFSync() {
fmt.Println("Testing rename.")
self.writeOrigFile()
f, err := os.Open(self.mountFile, os.O_WRONLY, 0)
_, err = f.WriteString("hello there")
if err != nil {
self.tester.Errorf("writestring %v", err)
}
// How to really test fsync ?
errNo := syscall.Fsync(f.Fd())
if errNo != 0 {
self.tester.Errorf("fsync returned %v", errNo)
}
f.Close()
}
// Test driver.
func TestMount(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
ts.testOpenUnreadable()
ts.testReadThroughFuse()
ts.testRemove()
ts.testMkdirRmdir()
ts.testLink()
ts.testSymlink()
ts.testRename()
ts.testAccess()
ts.testMknod()
ts.testReaddir()
ts.testFSync()
ts.Cleanup()
}
package fuse
import (
"bytes"
"sync"
"fmt"
"path"
"strings"
)
type inodeData struct {
Parent *inodeData
NodeId uint64
Name string
Count int
}
func (self *inodeData) GetPath() string {
// TODO - softcode this.
var components [100]string
j := len(components)
for p := self; p != nil && p.NodeId != FUSE_ROOT_ID; p = p.Parent {
j--
components[j] = p.Name
}
fullPath := strings.Join(components[j:], "/")
return fullPath
}
// Should implement some hash table method instead?
func inodeDataKey(parentInode uint64, name string) string {
return fmt.Sprintf("%x:%s", parentInode, name)
}
type PathFileSystemConnector struct {
fileSystem PathFuseFilesystem
// Protects the hashmap and its contents.
lock sync.Mutex
inodePathMap map[string]*inodeData
inodePathMapByInode map[uint64]*inodeData
nextFreeInode uint64
}
func NewPathFileSystemConnector(fs PathFuseFilesystem) (out *PathFileSystemConnector) {
out = new(PathFileSystemConnector)
out.inodePathMap = make(map[string]*inodeData)
out.inodePathMapByInode = make(map[uint64]*inodeData)
out.fileSystem = fs
rootData := new(inodeData)
rootData.Count = 1
rootData.NodeId = FUSE_ROOT_ID
out.inodePathMap[inodeDataKey(0, "")] = rootData
out.inodePathMapByInode[FUSE_ROOT_ID] = rootData
out.nextFreeInode = FUSE_ROOT_ID + 1
return out
}
func (self *PathFileSystemConnector) Init(h *InHeader, input *InitIn) (*InitOut, Status) {
return self.fileSystem.Init()
}
func (self *PathFileSystemConnector) Destroy(h *InHeader, input *InitIn) {
self.fileSystem.Destroy()
}
func (self *PathFileSystemConnector) Lookup(header *InHeader, name string) (out *EntryOut, status Status) {
self.lock.Lock()
defer self.lock.Unlock()
parent, ok := self.inodePathMapByInode[header.NodeId]
if !ok {
panic("Parent inode unknown.")
}
fullPath := path.Join(parent.GetPath(), name)
attr, err := self.fileSystem.GetAttr(fullPath)
if err != OK {
return nil, err
}
key := inodeDataKey(header.NodeId, name)
data, ok := self.inodePathMap[key]
if !ok {
data = new(inodeData)
data.Parent = parent
data.NodeId = self.nextFreeInode
data.Name = name
data.Count = 0
self.nextFreeInode++
self.inodePathMapByInode[data.NodeId] = data
self.inodePathMap[key] = data
}
data.Count++
out = new(EntryOut)
out.NodeId = data.NodeId
out.Generation = 1 // where to get the generation?
out.EntryValid = 0
out.AttrValid = 0
out.EntryValidNsec = 0
out.AttrValidNsec = 0
out.Attr = *attr
return out, OK
}
func (self *PathFileSystemConnector) getInodeData(nodeid uint64) *inodeData {
self.lock.Lock()
defer self.lock.Unlock()
return self.inodePathMapByInode[nodeid]
}
func (self *PathFileSystemConnector) GetPath(nodeid uint64) string {
return self.getInodeData(nodeid).GetPath()
}
func (self *PathFileSystemConnector) Forget(h *InHeader, input *ForgetIn) {
self.lock.Lock()
defer self.lock.Unlock()
data, ok := self.inodePathMapByInode[h.NodeId]
if ok {
data.Count -= int(input.Nlookup)
if data.Count <= 0 {
self.inodePathMapByInode[h.NodeId] = nil, false
var p uint64
p = 0
if data.Parent != nil {
p = data.Parent.NodeId
}
self.inodePathMap[inodeDataKey(p, data.Name)] = nil, false
}
}
}
func (self *PathFileSystemConnector) GetAttr(header *InHeader, input *GetAttrIn) (out *AttrOut, code Status) {
attr, err := self.fileSystem.GetAttr(self.GetPath(header.NodeId))
if err != OK {
return nil, err
}
out = new(AttrOut)
out.Attr = *attr
// TODO - how to configure Valid timespans?
out.AttrValid = 0
// 0.1 second
out.AttrValidNsec = 100e3
return out, OK
}
func (self *PathFileSystemConnector) OpenDir(header *InHeader, input *OpenIn) (flags uint32, fuseFile RawFuseDir, status Status) {
// TODO - how to handle return flags, the FUSE open flags?
f, err := self.fileSystem.OpenDir(self.GetPath(header.NodeId))
if err != OK {
return 0, nil, err
}
return 0, f, OK
}
func (self *PathFileSystemConnector) Open(header *InHeader, input *OpenIn) (flags uint32, fuseFile RawFuseFile, status Status) {
// TODO - how to handle return flags, the FUSE open flags?
f, err := self.fileSystem.Open(self.GetPath(header.NodeId), input.Flags)
if err != OK {
return 0, nil, err
}
return 0, f, OK
}
func (self *PathFileSystemConnector) SetAttr(header *InHeader, input *SetAttrIn) (out *AttrOut, code Status) {
var err Status = OK
// TODO - support Fh. (FSetAttr/FGetAttr/FTruncate.)
fullPath := self.GetPath(header.NodeId)
if input.Valid&FATTR_MODE != 0 {
err = self.fileSystem.Chmod(fullPath, input.Mode)
}
if err != OK && (input.Valid&FATTR_UID != 0 || input.Valid&FATTR_GID != 0) {
// TODO - can we get just FATTR_GID but not FATTR_UID ?
err = self.fileSystem.Chown(fullPath, uint32(input.Uid), uint32(input.Gid))
}
if input.Valid&FATTR_SIZE != 0 {
self.fileSystem.Truncate(fullPath, input.Size)
}
if err != OK && (input.Valid&FATTR_ATIME != 0 || input.Valid&FATTR_MTIME != 0) {
err = self.fileSystem.Utimens(fullPath,
uint64(input.Atime*1e9)+uint64(input.Atimensec),
uint64(input.Mtime*1e9)+uint64(input.Mtimensec))
}
if err != OK && (input.Valid&FATTR_ATIME_NOW != 0 || input.Valid&FATTR_MTIME_NOW != 0) {
// TODO - should set time to now. Maybe just reuse
// Utimens() ? Go has no UTIME_NOW unfortunately.
}
if err != OK {
return nil, err
}
// TODO - where to get GetAttrIn.Flags / Fh ?
return self.GetAttr(header, new(GetAttrIn))
}
func (self *PathFileSystemConnector) Readlink(header *InHeader) (out []byte, code Status) {
fullPath := self.GetPath(header.NodeId)
val, err := self.fileSystem.Readlink(fullPath)
return bytes.NewBufferString(val).Bytes(), err
}
func (self *PathFileSystemConnector) Mknod(header *InHeader, input *MknodIn, name string) (out *EntryOut, code Status) {
fullPath := path.Join(self.GetPath(header.NodeId), name)
err := self.fileSystem.Mknod(fullPath, input.Mode, uint32(input.Rdev))
if err != OK {
return nil, err
}
return self.Lookup(header, name)
}
func (self *PathFileSystemConnector) Mkdir(header *InHeader, input *MkdirIn, name string) (out *EntryOut, code Status) {
err := self.fileSystem.Mkdir(path.Join(self.GetPath(header.NodeId), name), input.Mode)
if err != OK {
return nil, err
}
out, code = self.Lookup(header, name)
return out, code
}
func (self *PathFileSystemConnector) Unlink(header *InHeader, name string) (code Status) {
return self.fileSystem.Unlink(path.Join(self.GetPath(header.NodeId), name))
}
func (self *PathFileSystemConnector) Rmdir(header *InHeader, name string) (code Status) {
return self.fileSystem.Rmdir(path.Join(self.GetPath(header.NodeId), name))
}
func (self *PathFileSystemConnector) Symlink(header *InHeader, pointedTo string, linkName string) (out *EntryOut, code Status) {
err := self.fileSystem.Symlink(pointedTo, path.Join(self.GetPath(header.NodeId), linkName))
if err != OK {
return nil, err
}
out, code = self.Lookup(header, linkName)
return out, code
}
func (self *PathFileSystemConnector) Rename(header *InHeader, input *RenameIn, oldName string, newName string) (code Status) {
// TODO - should also update the path <-> inode mapping?
oldPath := path.Join(self.GetPath(header.NodeId), oldName)
newPath := path.Join(self.GetPath(input.Newdir), newName)
return self.fileSystem.Rename(oldPath, newPath)
}
func (self *PathFileSystemConnector) Link(header *InHeader, input *LinkIn, filename string) (out *EntryOut, code Status) {
orig := self.GetPath(input.Oldnodeid)
newName := path.Join(self.GetPath(header.NodeId), filename)
err := self.fileSystem.Link(orig, newName)
if err != OK {
return nil, err
}
return self.Lookup(header, filename)
}
func (self *PathFileSystemConnector) Access(header *InHeader, input *AccessIn) (code Status) {
return self.fileSystem.Access(self.GetPath(header.NodeId), input.Mask)
}
func (self *PathFileSystemConnector) Create(header *InHeader, input *CreateIn, name string) (flags uint32, fuseFile RawFuseFile, out *EntryOut, code Status) {
directory := self.GetPath(header.NodeId)
fullPath := path.Join(directory, name)
f, err := self.fileSystem.Create(fullPath, uint32(input.Flags), input.Mode)
if err != OK {
return 0, nil, nil, err
}
out, code = self.Lookup(header, name)
return 0, f, out, code
}
////////////////////////////////////////////////////////////////
// unimplemented.
func (self *PathFileSystemConnector) SetXAttr(header *InHeader, input *SetXAttrIn) Status {
return ENOSYS
}
func (self *PathFileSystemConnector) GetXAttr(header *InHeader, input *GetXAttrIn) (out *GetXAttrOut, code Status) {
return nil, ENOSYS
}
func (self *PathFileSystemConnector) Bmap(header *InHeader, input *BmapIn) (out *BmapOut, code Status) {
return nil, ENOSYS
}
func (self *PathFileSystemConnector) Ioctl(header *InHeader, input *IoctlIn) (out *IoctlOut, code Status) {
return nil, ENOSYS
}
func (self *PathFileSystemConnector) Poll(header *InHeader, input *PollIn) (out *PollOut, code Status) {
return nil, ENOSYS
}
......@@ -2,6 +2,7 @@ package fuse
import (
"syscall"
"bytes"
)
const (
......@@ -11,6 +12,7 @@ const (
FUSE_ROOT_ID = 1
// SetAttrIn.Valid
FATTR_MODE = (1 << 0)
FATTR_UID = (1 << 1)
FATTR_GID = (1 << 2)
......@@ -22,10 +24,12 @@ const (
FATTR_MTIME_NOW = (1 << 8)
FATTR_LOCKOWNER = (1 << 9)
// OpenIn.Flags
FOPEN_DIRECT_IO = (1 << 0)
FOPEN_KEEP_CACHE = (1 << 1)
FOPEN_NONSEEKABLE = (1 << 2)
// To be set in InitOut.Flags.
FUSE_ASYNC_READ = (1 << 0)
FUSE_POSIX_LOCKS = (1 << 1)
FUSE_FILE_OPS = (1 << 2)
......@@ -270,6 +274,11 @@ type OpenOut struct {
Padding uint32
}
type CreateOut struct {
Entry EntryOut
Open OpenOut
}
type ReleaseIn struct {
Fh uint64
Flags uint32
......@@ -294,7 +303,6 @@ type ReadIn struct {
Padding uint32
}
type WriteIn struct {
Fh uint64
Offset uint64
......@@ -470,3 +478,103 @@ type NotifyInvalEntryOut struct {
NameLen uint32
Padding uint32
}
////////////////////////////////////////////////////////////////
// Types for users to implement.
// This is the interface to the file system, mirroring the interface from
//
// /usr/include/fuse/fuse_lowlevel.h
//
// Typically, each call happens in its own goroutine, so any global data should be
// made thread-safe.
type RawFileSystem interface {
Init(h *InHeader, input *InitIn) (out *InitOut, code Status)
Destroy(h *InHeader, input *InitIn)
Lookup(header *InHeader, name string) (out *EntryOut, status Status)
Forget(header *InHeader, input *ForgetIn)
GetAttr(header *InHeader, input *GetAttrIn) (out *AttrOut, code Status)
SetAttr(header *InHeader, input *SetAttrIn) (out *AttrOut, code Status)
Readlink(header *InHeader) (out []byte, code Status)
Mknod(header *InHeader, input *MknodIn, name string) (out *EntryOut, code Status)
Mkdir(header *InHeader, input *MkdirIn, name string) (out *EntryOut, code Status)
Unlink(header *InHeader, name string) (code Status)
Rmdir(header *InHeader, name string) (code Status)
Symlink(header *InHeader, pointedTo string, linkName string) (out *EntryOut, code Status)
Rename(header *InHeader, input *RenameIn, oldName string, newName string) (code Status)
Link(header *InHeader, input *LinkIn, filename string) (out *EntryOut, code Status)
// Unused:
SetXAttr(header *InHeader, input *SetXAttrIn) Status
GetXAttr(header *InHeader, input *GetXAttrIn) (out *GetXAttrOut, code Status)
Access(header *InHeader, input *AccessIn) (code Status)
Create(header *InHeader, input *CreateIn, name string) (flags uint32, fuseFile RawFuseFile, out *EntryOut, code Status)
Bmap(header *InHeader, input *BmapIn) (out *BmapOut, code Status)
Ioctl(header *InHeader, input *IoctlIn) (out *IoctlOut, code Status)
Poll(header *InHeader, input *PollIn) (out *PollOut, code Status)
// The return flags are FOPEN_xx.
Open(header *InHeader, input *OpenIn) (flags uint32, fuseFile RawFuseFile, status Status)
OpenDir(header *InHeader, input *OpenIn) (flags uint32, fuseFile RawFuseDir, status Status)
}
type RawFuseFile interface {
Read(*ReadIn) ([]byte, Status)
// u32 <-> u64 ?
Write(*WriteIn, []byte) (uint32, Status)
Flush() Status
Release()
Fsync(*FsyncIn) (code Status)
}
type RawFuseDir interface {
ReadDir(input *ReadIn) (*DEntryList, Status)
ReleaseDir()
FsyncDir(input *FsyncIn) (code Status)
}
// Should make interface ?
type DEntryList struct {
buf bytes.Buffer
offset uint64
}
type PathFuseFilesystem interface {
GetAttr(name string) (*Attr, Status)
Readlink(name string) (string, Status)
Mknod(name string, mode uint32, dev uint32) Status
Mkdir(name string, mode uint32) Status
Unlink(name string) (code Status)
Rmdir(name string) (code Status)
Symlink(value string, linkName string) (code Status)
Rename(oldName string, newName string) (code Status)
Link(oldName string, newName string) (code Status)
Chmod(name string, mode uint32) (code Status)
Chown(name string, uid uint32, gid uint32) (code Status)
Truncate(name string, offset uint64) (code Status)
Open(name string, flags uint32) (file RawFuseFile, code Status)
// Where to hook up statfs?
//
// Unimplemented:
// RemoveXAttr, SetXAttr, GetXAttr, ListXAttr.
OpenDir(name string) (dir RawFuseDir, code Status)
// TODO - what is a good interface?
Init() (*InitOut, Status)
Destroy()
Access(name string, mode uint32) (code Status)
Create(name string, flags uint32, mode uint32) (file RawFuseFile, code Status)
Utimens(name string, AtimeNs uint64, CtimeNs uint64) (code Status)
// unimplemented: poll, ioctl, bmap.
}
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