Commit 64b9da5a authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

fuse/nodefs: add LookupKnownChildren mount option

When set, all Lookup calls will be forwarded from the kernel, even if
the current tree already has a child with the given name.

This allows the tree to be reconfigured driven by kernel events.

Fixes #211.
parent 02b38384
...@@ -38,7 +38,8 @@ type Node interface { ...@@ -38,7 +38,8 @@ type Node interface {
OnUnmount() OnUnmount()
// Lookup finds a child node to this node; it is only called // Lookup finds a child node to this node; it is only called
// for directory Nodes. // for directory Nodes. Lookup may be called on nodes that are
// already known.
Lookup(out *fuse.Attr, name string, context *fuse.Context) (*Inode, fuse.Status) Lookup(out *fuse.Attr, name string, context *fuse.Context) (*Inode, fuse.Status)
// Deletable() should return true if this node may be discarded once // Deletable() should return true if this node may be discarded once
...@@ -187,4 +188,9 @@ type Options struct { ...@@ -187,4 +188,9 @@ type Options struct {
// If set, print debug information. // If set, print debug information.
Debug bool Debug bool
// If set, issue Lookup rather than GetAttr calls for known
// children. This allows the filesystem to update its inode
// hierarchy in response to kernel calls.
LookupKnownChildren bool
} }
...@@ -85,7 +85,7 @@ func (c *FileSystemConnector) internalLookup(out *fuse.Attr, parent *Inode, name ...@@ -85,7 +85,7 @@ func (c *FileSystemConnector) internalLookup(out *fuse.Attr, parent *Inode, name
return c.lookupMountUpdate(out, child.mountPoint) return c.lookupMountUpdate(out, child.mountPoint)
} }
if child != nil { if child != nil && !parent.mount.options.LookupKnownChildren {
code = child.fsInode.GetAttr(out, nil, &header.Context) code = child.fsInode.GetAttr(out, nil, &header.Context)
} else { } else {
child, code = parent.fsInode.Lookup(out, name, &header.Context) child, code = parent.fsInode.Lookup(out, name, &header.Context)
......
...@@ -29,12 +29,13 @@ func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) { ...@@ -29,12 +29,13 @@ func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) {
connector := NewFileSystemConnector(root, connector := NewFileSystemConnector(root,
&Options{ &Options{
EntryTimeout: testTtl, EntryTimeout: testTtl,
AttrTimeout: testTtl, AttrTimeout: testTtl,
NegativeTimeout: 0.0, NegativeTimeout: 0.0,
Debug: testutil.VerboseTest(), Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
}) })
state, err := fuse.NewServer(connector.RawFS(), mnt, &fuse.MountOptions{Debug: testutil.VerboseTest()}) state, err5 := fuse.NewServer(connector.RawFS(), mnt, &fuse.MountOptions{Debug: testutil.VerboseTest()})
if err != nil { if err != nil {
t.Fatal("NewServer", err) t.Fatal("NewServer", err)
} }
......
...@@ -541,10 +541,17 @@ func (n *pathInode) Open(flags uint32, context *fuse.Context) (file nodefs.File, ...@@ -541,10 +541,17 @@ func (n *pathInode) Open(flags uint32, context *fuse.Context) (file nodefs.File,
return return
} }
func (n *pathInode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (node *nodefs.Inode, code fuse.Status) { func (n *pathInode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (*nodefs.Inode, fuse.Status) {
fullPath := filepath.Join(n.GetPath(), name) fullPath := filepath.Join(n.GetPath(), name)
fi, code := n.fs.GetAttr(fullPath, context) fi, code := n.fs.GetAttr(fullPath, context)
if code.Ok() { node := n.Inode().GetChild(name)
if node != nil && (!code.Ok() || node.IsDir() != fi.IsDir()) {
n.Inode().RmChild(name)
node = nil
}
if code.Ok() && node == nil {
node = n.findChild(fi, name, fullPath).Inode() node = n.findChild(fi, name, fullPath).Inode()
*out = *fi *out = *fi
} }
......
...@@ -95,10 +95,11 @@ func NewTestCase(t *testing.T) *testCase { ...@@ -95,10 +95,11 @@ func NewTestCase(t *testing.T) *testCase {
ClientInodes: true}) ClientInodes: true})
tc.connector = nodefs.NewFileSystemConnector(tc.pathFs.Root(), tc.connector = nodefs.NewFileSystemConnector(tc.pathFs.Root(),
&nodefs.Options{ &nodefs.Options{
EntryTimeout: testTtl, EntryTimeout: testTtl,
AttrTimeout: testTtl, AttrTimeout: testTtl,
NegativeTimeout: 0.0, NegativeTimeout: 0.0,
Debug: testutil.VerboseTest(), Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
}) })
tc.state, err = fuse.NewServer( tc.state, err = fuse.NewServer(
fuse.NewRawFileSystem(tc.connector.RawFS()), tc.mnt, &fuse.MountOptions{ fuse.NewRawFileSystem(tc.connector.RawFS()), tc.mnt, &fuse.MountOptions{
......
// Copyright 2018 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package test
import (
"os"
"sync"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
)
type rootNode struct {
nodefs.Node
// represents backing store.
mu sync.Mutex
backing map[string]string
}
type blobNode struct {
nodefs.Node
content string
}
func (n *blobNode) GetAttr(out *fuse.Attr, file nodefs.File, context *fuse.Context) (code fuse.Status) {
out.Mode = fuse.S_IFREG | 0777
out.Size = uint64(len(n.content))
return fuse.OK
}
func (n *rootNode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (*nodefs.Inode, fuse.Status) {
n.mu.Lock()
defer n.mu.Unlock()
want := n.backing[name]
if want == "" {
return nil, fuse.ENOENT
}
ch := n.Inode().GetChild(name)
var blob *blobNode
if ch != nil {
blob = ch.Node().(*blobNode)
if blob.content != want {
n.Inode().RmChild(name)
ch = nil
}
}
if ch == nil {
blob = &blobNode{nodefs.NewDefaultNode(), want}
ch = n.Inode().NewChild(name, false, blob)
}
status := blob.GetAttr(out, nil, nil)
return ch, status
}
func TestUpdateNode(t *testing.T) {
dir := testutil.TempDir()
root := &rootNode{
Node: nodefs.NewDefaultNode(),
backing: map[string]string{"a": "aaa"},
}
opts := nodefs.NewOptions()
opts.Debug = testutil.VerboseTest()
opts.EntryTimeout = 0
opts.LookupKnownChildren = true
server, _, err := nodefs.MountRoot(dir, root, opts)
if err != nil {
t.Fatalf("MountRoot: %v", err)
}
go server.Serve()
if err := server.WaitMount(); err != nil {
t.Fatalf("WaitMount: %v", err)
}
defer server.Unmount()
fi1, err := os.Lstat(dir + "/a")
if err != nil {
t.Fatal("Lstat", err)
}
if fi1.Size() != 3 {
t.Fatalf("got %v, want sz 3", fi1.Size())
}
root.mu.Lock()
root.backing["a"] = "x"
root.mu.Unlock()
fi2, err := os.Lstat(dir + "/a")
if err != nil {
t.Fatal("Lstat", err)
}
if fi2.Size() != 1 {
t.Fatalf("got %#v, want sz 1", fi2)
}
}
...@@ -21,10 +21,11 @@ const entryTtl = 100 * time.Millisecond ...@@ -21,10 +21,11 @@ const entryTtl = 100 * time.Millisecond
var testAOpts = AutoUnionFsOptions{ var testAOpts = AutoUnionFsOptions{
UnionFsOptions: testOpts, UnionFsOptions: testOpts,
Options: nodefs.Options{ Options: nodefs.Options{
EntryTimeout: entryTtl, EntryTimeout: entryTtl,
AttrTimeout: entryTtl, AttrTimeout: entryTtl,
NegativeTimeout: 0, NegativeTimeout: 0,
Debug: testutil.VerboseTest(), Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
}, },
HideReadonly: true, HideReadonly: true,
Version: "version", Version: "version",
......
...@@ -91,11 +91,12 @@ func setupUfs(t *testing.T) (wd string, cleanup func()) { ...@@ -91,11 +91,12 @@ func setupUfs(t *testing.T) (wd string, cleanup func()) {
// We configure timeouts are smaller, so we can check for // We configure timeouts are smaller, so we can check for
// UnionFs's cache consistency. // UnionFs's cache consistency.
opts := &nodefs.Options{ opts := &nodefs.Options{
EntryTimeout: entryTtl / 2, EntryTimeout: entryTtl / 2,
AttrTimeout: entryTtl / 2, AttrTimeout: entryTtl / 2,
NegativeTimeout: entryTtl / 2, NegativeTimeout: entryTtl / 2,
PortableInodes: true, PortableInodes: true,
Debug: testutil.VerboseTest(), Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
} }
pathfs := pathfs.NewPathNodeFs(ufs, pathfs := pathfs.NewPathNodeFs(ufs,
...@@ -1158,10 +1159,11 @@ func TestUnionFsDisappearing(t *testing.T) { ...@@ -1158,10 +1159,11 @@ func TestUnionFsDisappearing(t *testing.T) {
} }
opts := &nodefs.Options{ opts := &nodefs.Options{
EntryTimeout: entryTtl, EntryTimeout: entryTtl,
AttrTimeout: entryTtl, AttrTimeout: entryTtl,
NegativeTimeout: entryTtl, NegativeTimeout: entryTtl,
Debug: testutil.VerboseTest(), Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
} }
nfs := pathfs.NewPathNodeFs(ufs, nil) nfs := pathfs.NewPathNodeFs(ufs, nil)
......
...@@ -60,10 +60,11 @@ func TestXAttrCaching(t *testing.T) { ...@@ -60,10 +60,11 @@ func TestXAttrCaching(t *testing.T) {
} }
opts := &nodefs.Options{ opts := &nodefs.Options{
EntryTimeout: entryTtl / 2, EntryTimeout: entryTtl / 2,
AttrTimeout: entryTtl / 2, AttrTimeout: entryTtl / 2,
NegativeTimeout: entryTtl / 2, NegativeTimeout: entryTtl / 2,
Debug: testutil.VerboseTest(), Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
} }
pathfs := pathfs.NewPathNodeFs(ufs, pathfs := pathfs.NewPathNodeFs(ufs,
......
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