Commit 7e917a7b authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

nodefs: NotifyXxxx functions

parent 02502040
......@@ -14,22 +14,31 @@ Decisions
* Nodes can be "persistent", meaning their lifetime is not under
control of the kernel. This is useful for constructing FS trees
in advance.
in advance, rather than driven by LOOKUP..
* The NodeID for FS tree node must be defined on creation and are
immutable. By contrast, reusing NodeIds (eg. rsc/bazil FUSE, as
well as old go-fuse/fuse/nodefs) is racy when notify and FORGET
operations race.
* The mode of an Inode is defined on creation. Files cannot change
type during their lifetime. This also prevents the common error
of forgetting to return the filetype in Lookup/GetAttr.
* The NodeID (used for communicating with kernel) is equal to
Attr.Ino (value shown in Stat and Lstat return values.)
Attr.Ino (value shown in Stat and Lstat return values.).
* No global treelock, to ensure scalability.
* Immutable characteristics of the Inode are passed on
creation. These are {NodeID, Mode}. Files cannot change type
during their lifetime. It also prevents the common error of
forgetting to return the filetype in Lookup/GetAttr.
* Support for hard links. libfuse doesn't support this in the
high-level API. Extra care for race conditions is needed when
looking up the same file different paths.
* do not issue Notify{Entry,Delete} as part of
AddChild/RmChild/MvChild: because NodeIDs are unique and
immutable, there is no confusion about which nodes are
invalidated, and the notification doesn't have to happen under
lock.
To decide
=========
......@@ -71,3 +80,6 @@ To decide
* Should Operations.Lookup return *Inode or Operations ?
* Should bridge.Lookup() add the child, bridge.Unlink remove the child, etc.?
......@@ -23,6 +23,7 @@ type fileEntry struct {
type rawBridge struct {
options Options
root *Inode
server *fuse.Server
// mu protects the following data. Locks for inodes must be
// taken before rawBridge.mu
......@@ -539,5 +540,6 @@ func (b *rawBridge) StatFs(input *fuse.InHeader, out *fuse.StatfsOut) (code fuse
return
}
func (b *rawBridge) Init(*fuse.Server) {
func (b *rawBridge) Init(s *fuse.Server) {
b.server = s
}
......@@ -11,6 +11,8 @@ import (
"strings"
"sync"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
)
var _ = log.Println
......@@ -519,3 +521,27 @@ retry:
return
}
}
func (n *Inode) NotifyEntry(name string) fuse.Status {
return n.bridge.server.EntryNotify(n.nodeID.Ino, name)
}
// XXX DeleteNotify ?
func (n *Inode) NotifyDelete(name string, child *Inode) fuse.Status {
// XXX arg ordering?
return n.bridge.server.DeleteNotify(n.nodeID.Ino, child.nodeID.Ino, name)
}
func (n *Inode) NotifyContent(off, sz int64) fuse.Status {
return n.bridge.server.InodeNotify(n.nodeID.Ino, off, sz)
}
func (n *Inode) WriteCache(offset int64, data []byte) fuse.Status {
return n.bridge.server.InodeNotifyStoreCache(n.nodeID.Ino, offset, data)
}
func (n *Inode) ReadCache(offset int64, dest []byte) (count int, code fuse.Status) {
return n.bridge.server.InodeRetrieveCache(n.nodeID.Ino, offset, dest)
}
......@@ -10,6 +10,7 @@ import (
"log"
"os"
"path/filepath"
"reflect"
"runtime"
"sync"
"syscall"
......@@ -31,6 +32,7 @@ type testCase struct {
origDir string
mntDir string
loopback Operations
rawFS fuse.RawFileSystem
server *fuse.Server
}
......@@ -65,10 +67,10 @@ func newTestCase(t *testing.T) *testCase {
t.Fatal(err)
}
loopback := NewLoopback(tc.origDir)
tc.loopback = NewLoopback(tc.origDir)
_ = time.Second
oneSec := time.Second
tc.rawFS = NewNodeFS(loopback, &Options{
tc.rawFS = NewNodeFS(tc.loopback, &Options{
Debug: testutil.VerboseTest(),
// NOSUBMIT - should run all tests without cache too
......@@ -454,3 +456,42 @@ func TestLink(t *testing.T) {
t.Errorf("Lstat after: got %d, want %d", st.Ino, beforeIno)
}
}
func TestNotifyEntry(t *testing.T) {
tc := newTestCase(t)
defer tc.Clean()
orig := tc.origDir + "/file"
fn := tc.mntDir + "/file"
if err := ioutil.WriteFile(orig, []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
st := syscall.Stat_t{}
if err := syscall.Lstat(fn, &st); err != nil {
t.Fatalf("Lstat before: %v", err)
}
if err := os.Remove(orig); err != nil {
t.Fatalf("Remove: %v", err)
}
after := syscall.Stat_t{}
if err := syscall.Lstat(fn, &after); err != nil {
t.Fatalf("Lstat after: %v", err)
} else if !reflect.DeepEqual(st, after) {
t.Fatalf("got after %#v, want %#v", after, st)
}
if code := InodeOf(tc.loopback).NotifyEntry("file"); !code.Ok() {
t.Errorf("notify failed: %v", code)
}
if err := syscall.Lstat(fn, &after); err != syscall.ENOENT {
t.Fatalf("Lstat after: got %v, want ENOENT", err)
}
}
// Test Notify() , but requires KEEP_CACHE.
// Test NotifyDelete?
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