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

nodefs: NotifyXxxx functions

parent 02502040
...@@ -14,22 +14,31 @@ Decisions ...@@ -14,22 +14,31 @@ Decisions
* Nodes can be "persistent", meaning their lifetime is not under * Nodes can be "persistent", meaning their lifetime is not under
control of the kernel. This is useful for constructing FS trees 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 * 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. * 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 * Support for hard links. libfuse doesn't support this in the
high-level API. Extra care for race conditions is needed when high-level API. Extra care for race conditions is needed when
looking up the same file different paths. 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 To decide
========= =========
...@@ -71,3 +80,6 @@ To decide ...@@ -71,3 +80,6 @@ To decide
* Should Operations.Lookup return *Inode or Operations ? * Should Operations.Lookup return *Inode or Operations ?
* Should bridge.Lookup() add the child, bridge.Unlink remove the child, etc.? * Should bridge.Lookup() add the child, bridge.Unlink remove the child, etc.?
...@@ -23,6 +23,7 @@ type fileEntry struct { ...@@ -23,6 +23,7 @@ type fileEntry struct {
type rawBridge struct { type rawBridge struct {
options Options options Options
root *Inode root *Inode
server *fuse.Server
// mu protects the following data. Locks for inodes must be // mu protects the following data. Locks for inodes must be
// taken before rawBridge.mu // taken before rawBridge.mu
...@@ -539,5 +540,6 @@ func (b *rawBridge) StatFs(input *fuse.InHeader, out *fuse.StatfsOut) (code fuse ...@@ -539,5 +540,6 @@ func (b *rawBridge) StatFs(input *fuse.InHeader, out *fuse.StatfsOut) (code fuse
return return
} }
func (b *rawBridge) Init(*fuse.Server) { func (b *rawBridge) Init(s *fuse.Server) {
b.server = s
} }
...@@ -11,6 +11,8 @@ import ( ...@@ -11,6 +11,8 @@ import (
"strings" "strings"
"sync" "sync"
"unsafe" "unsafe"
"github.com/hanwen/go-fuse/fuse"
) )
var _ = log.Println var _ = log.Println
...@@ -519,3 +521,27 @@ retry: ...@@ -519,3 +521,27 @@ retry:
return 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 ( ...@@ -10,6 +10,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"sync" "sync"
"syscall" "syscall"
...@@ -31,8 +32,9 @@ type testCase struct { ...@@ -31,8 +32,9 @@ type testCase struct {
origDir string origDir string
mntDir string mntDir string
rawFS fuse.RawFileSystem loopback Operations
server *fuse.Server rawFS fuse.RawFileSystem
server *fuse.Server
} }
func (tc *testCase) writeOrig(path, content string, mode os.FileMode) { func (tc *testCase) writeOrig(path, content string, mode os.FileMode) {
...@@ -65,10 +67,10 @@ func newTestCase(t *testing.T) *testCase { ...@@ -65,10 +67,10 @@ func newTestCase(t *testing.T) *testCase {
t.Fatal(err) t.Fatal(err)
} }
loopback := NewLoopback(tc.origDir) tc.loopback = NewLoopback(tc.origDir)
_ = time.Second _ = time.Second
oneSec := time.Second oneSec := time.Second
tc.rawFS = NewNodeFS(loopback, &Options{ tc.rawFS = NewNodeFS(tc.loopback, &Options{
Debug: testutil.VerboseTest(), Debug: testutil.VerboseTest(),
// NOSUBMIT - should run all tests without cache too // NOSUBMIT - should run all tests without cache too
...@@ -454,3 +456,42 @@ func TestLink(t *testing.T) { ...@@ -454,3 +456,42 @@ func TestLink(t *testing.T) {
t.Errorf("Lstat after: got %d, want %d", st.Ino, beforeIno) 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