Commit 8acbd4b0 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Add inode notify.

This can be used to advise the kernel of updated attributes or data of
existing data; it cannot be used to remove negative entries.
parent bee7c066
......@@ -8,7 +8,6 @@ import (
// Types for users to implement.
// A filesystem API that uses paths rather than inodes. A minimal
// file system should have at least a functional GetAttr method.
// Typically, each call happens in its own goroutine, so take care to
......@@ -161,7 +160,18 @@ type RawFileSystem interface {
//
Ioctl(header *InHeader, input *IoctlIn) (output *IoctlOut, data []byte, code Status)
// Provide callbacks for pushing notifications to the kernel.
Init(params *RawFsInit)
}
// DefaultRawFileSystem returns ENOSYS for every operation.
type DefaultRawFileSystem struct{}
// Talk back to FUSE.
//
// TODO - implement EntryNotify. Currently, EntryNotify causes a
// kernel error.
type RawFsInit struct {
InodeNotify func(*NotifyInvalInodeOut) Status
}
......@@ -9,6 +9,9 @@ import (
var _ = log.Println
var _ = fmt.Println
func (me *DefaultRawFileSystem) Init(init *RawFsInit) {
}
func (me *DefaultRawFileSystem) Lookup(h *InHeader, name string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
......
......@@ -14,10 +14,14 @@ import (
"io/ioutil"
)
func (code Status) String() string {
if code.Ok() {
return "OK"
if code <= 0 {
return []string{
"OK",
"NOTIFY_POLL",
"NOTIFY_INVAL_INODE",
"NOTIFY_INVAL_ENTRY",
}[-code]
}
return fmt.Sprintf("%d=%v", int(code), os.Errno(code))
}
......@@ -82,7 +86,6 @@ func CopyFileInfo(fi *os.FileInfo, attr *Attr) {
attr.Blksize = uint32(fi.Blksize)
}
func writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, errno int) {
n1, _, e1 := syscall.Syscall(
syscall.SYS_WRITEV,
......@@ -127,7 +130,6 @@ func ModeToType(mode uint32) uint32 {
return (mode & 0170000) >> 12
}
func CheckSuccess(e os.Error) {
if e != nil {
panic(fmt.Sprintf("Unexpected error: %v", e))
......@@ -174,4 +176,3 @@ func CurrentOwner() *Owner {
Gid: uint32(os.Getgid()),
}
}
......@@ -5,6 +5,7 @@ import (
"os"
"syscall"
"time"
"unsafe"
)
const (
......@@ -63,6 +64,12 @@ func (me *MountState) Mount(mountPoint string, opts *MountOptions) os.Error {
if err != nil {
return err
}
initParams := RawFsInit{
InodeNotify: func(n *NotifyInvalInodeOut) Status {
return me.writeInodeNotify(n)
},
}
me.fileSystem.Init(&initParams)
me.mountPoint = mp
me.mountFile = file
return nil
......@@ -212,15 +219,19 @@ func (me *MountState) handleRequest(req *request) {
req.handler.Func(me, req)
}
me.write(req)
errNo := me.write(req)
if errNo != 0 {
log.Printf("writer: Write/Writev %v failed, err: %v. opcode: %v",
req.outHeaderBytes, errNo, operationName(req.inHeader.opcode))
}
}
func (me *MountState) write(req *request) {
func (me *MountState) write(req *request) Status {
// 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
if req.inHeader.opcode == _OP_FORGET {
return
return OK
}
req.serialize()
......@@ -233,7 +244,7 @@ func (me *MountState) write(req *request) {
}
if req.outHeaderBytes == nil {
return
return OK
}
var err os.Error
......@@ -244,8 +255,23 @@ func (me *MountState) write(req *request) {
[][]byte{req.outHeaderBytes, req.flatData})
}
if err != nil {
log.Printf("writer: Write/Writev %v failed, err: %v. opcode: %v",
req.outHeaderBytes, err, operationName(req.inHeader.opcode))
return OsErrorToErrno(err)
}
func (me *MountState) writeInodeNotify(entry *NotifyInvalInodeOut) Status {
req := request{
inHeader: &InHeader{
opcode: _OP_NOTIFY_INODE,
},
handler: operationHandlers[_OP_NOTIFY_INODE],
status: NOTIFY_INVAL_INODE,
}
req.outData = unsafe.Pointer(entry)
req.serialize()
result := me.write(&req)
if me.Debug {
log.Println("Response: INODE_NOTIFY", result)
}
return result
}
package fuse
import (
"log"
"os"
"testing"
)
var _ = log.Println
type NotifyFs struct {
DefaultFileSystem
size int64
exist bool
}
func (me *NotifyFs) GetAttr(name string) (*os.FileInfo, Status) {
if name == "file" || (name == "dir/file" && me.exist) {
return &os.FileInfo{Mode: S_IFREG | 0644, Size: me.size}, OK
}
if name == "dir" {
return &os.FileInfo{Mode: S_IFDIR | 0755}, OK
}
return nil, ENOENT
}
type NotifyTest struct {
fs *NotifyFs
connector *FileSystemConnector
dir string
state *MountState
}
func NewNotifyTest() *NotifyTest {
me := &NotifyTest{}
me.fs = &NotifyFs{}
me.dir = MakeTempDir()
entryTtl := 0.1
opts := &FileSystemOptions{
EntryTimeout: entryTtl,
AttrTimeout: entryTtl,
NegativeTimeout: entryTtl,
}
var err os.Error
me.state, me.connector, err = MountFileSystem(me.dir, me.fs, opts)
CheckSuccess(err)
me.state.Debug = true
go me.state.Loop(false)
return me
}
func (me *NotifyTest) Clean() {
err := me.state.Unmount()
if err == nil {
os.RemoveAll(me.dir)
}
}
func TestInodeNotify(t *testing.T) {
test := NewNotifyTest()
defer test.Clean()
fs := test.fs
dir := test.dir
fs.size = 42
fi, err := os.Lstat(dir + "/file")
CheckSuccess(err)
if !fi.IsRegular() || fi.Size != 42 {
t.Error(fi)
}
fs.size = 666
fi, err = os.Lstat(dir + "/file")
CheckSuccess(err)
if !fi.IsRegular() || fi.Size == 666 {
t.Error(fi)
}
code := test.connector.FileNotify("file", -1, 0)
if !code.Ok() {
t.Error(code)
}
fi, err = os.Lstat(dir + "/file")
CheckSuccess(err)
if !fi.IsRegular() || fi.Size != 666 {
t.Error(fi)
}
}
func TestInodeNotifyRemoval(t *testing.T) {
test := NewNotifyTest()
defer test.Clean()
fs := test.fs
dir := test.dir
fs.exist = true
fi, err := os.Lstat(dir + "/dir/file")
CheckSuccess(err)
if !fi.IsRegular() {
t.Error("IsRegular", fi)
}
fs.exist = false
fi, err = os.Lstat(dir + "/dir/file")
CheckSuccess(err)
code := test.connector.FileNotify("dir/file", -1, 0)
if !code.Ok() {
t.Error(code)
}
fi, err = os.Lstat(dir + "/dir/file")
if fi != nil {
t.Error("should have been removed", fi)
}
}
......@@ -52,9 +52,12 @@ const (
_OP_IOCTL = opcode(39)
_OP_POLL = opcode(40)
_OPCODE_COUNT = opcode(41)
)
// Ugh - what will happen if FUSE introduces a new opcode here?
_OP_NOTIFY_ENTRY = opcode(51)
_OP_NOTIFY_INODE = opcode(52)
_OPCODE_COUNT = opcode(53)
)
////////////////////////////////////////////////////////////////
......@@ -121,7 +124,6 @@ func doCreate(state *MountState, req *request) {
}
}
func doReadDir(state *MountState, req *request) {
entries, code := state.fileSystem.ReadDir(req.inHeader, (*ReadIn)(req.inData))
if entries != nil {
......@@ -130,7 +132,6 @@ func doReadDir(state *MountState, req *request) {
req.status = code
}
func doOpenDir(state *MountState, req *request) {
flags, handle, status := state.fileSystem.OpenDir(req.inHeader, (*OpenIn)(req.inData))
req.status = status
......@@ -157,7 +158,6 @@ func doWrite(state *MountState, req *request) {
req.status = status
}
func doGetXAttr(state *MountState, req *request) {
input := (*GetXAttrIn)(req.inData)
var data []byte
......@@ -379,6 +379,8 @@ func init() {
_OP_BMAP: unsafe.Sizeof(BmapOut{}),
_OP_IOCTL: unsafe.Sizeof(IoctlOut{}),
_OP_POLL: unsafe.Sizeof(PollOut{}),
_OP_NOTIFY_ENTRY: unsafe.Sizeof(NotifyInvalEntryOut{}),
_OP_NOTIFY_INODE: unsafe.Sizeof(NotifyInvalInodeOut{}),
} {
operationHandlers[op].OutputSize = sz
}
......@@ -421,7 +423,10 @@ func init() {
_OP_BMAP: "BMAP",
_OP_DESTROY: "DESTROY",
_OP_IOCTL: "IOCTL",
_OP_POLL: "POLL"} {
_OP_POLL: "POLL",
_OP_NOTIFY_ENTRY: "NOTIFY_ENTRY",
_OP_NOTIFY_INODE: "NOTIFY_INODE",
} {
operationHandlers[op].Name = v
}
......@@ -468,6 +473,8 @@ func init() {
_OP_SETATTR: func(ptr unsafe.Pointer) interface{} { return (*AttrOut)(ptr) },
_OP_INIT: func(ptr unsafe.Pointer) interface{} { return (*InitOut)(ptr) },
_OP_MKDIR: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) },
_OP_NOTIFY_ENTRY: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalEntryOut)(ptr) },
_OP_NOTIFY_INODE: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalInodeOut)(ptr) },
} {
operationHandlers[op].DecodeOut = f
}
......
......@@ -306,10 +306,15 @@ type FileSystemConnector struct {
Debug bool
fsInit RawFsInit
inodeMap HandleMap
rootNode *inode
}
func (me *FileSystemConnector) Init(fsInit *RawFsInit) {
me.fsInit = *fsInit
}
func (me *FileSystemConnector) Statistics() string {
return fmt.Sprintf("Inodes %20d\n", me.inodeMap.Count())
}
......@@ -630,3 +635,17 @@ func (me *FileSystemConnector) getOpenFileData(nodeid uint64, fh uint64) (f File
}
return
}
func (me *FileSystemConnector) FileNotify(path string, off int64, length int64) Status {
node := me.findInode(path)
if node == nil {
return ENOENT
}
out := NotifyInvalInodeOut{
Length: length,
Off: off,
Ino: node.NodeId,
}
return me.fsInit.InodeNotify(&out)
}
......@@ -125,7 +125,7 @@ func (me *request) parse() {
func (me *request) serialize() {
dataLength := me.handler.OutputSize
if me.outData == nil || me.status != OK {
if me.outData == nil || me.status > OK {
dataLength = 0
}
......
......@@ -54,14 +54,13 @@ const (
EXDEV = Status(syscall.EXDEV)
)
type NotifyCode int
const (
FUSE_NOTIFY_POLL = 1
FUSE_NOTIFY_INVAL_INODE = 2
FUSE_NOTIFY_INVAL_ENTRY = 3
FUSE_NOTIFY_CODE_MAX = 4
NOTIFY_POLL = -1
NOTIFY_INVAL_INODE = -2
NOTIFY_INVAL_ENTRY = -3
NOTIFY_CODE_MAX = -4
)
type Attr struct {
......@@ -164,7 +163,6 @@ type LinkIn struct {
Oldnodeid uint64
}
const ( // SetAttrIn.Valid
FATTR_MODE = (1 << 0)
FATTR_UID = (1 << 1)
......@@ -254,7 +252,6 @@ type ReadIn struct {
Padding uint32
}
const (
FUSE_WRITE_CACHE = (1 << 0)
FUSE_WRITE_LOCKOWNER = (1 << 1)
......
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