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)
}
This diff is collapsed.
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