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 {
OnUnmount()
// 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)
// Deletable() should return true if this node may be discarded once
......@@ -187,4 +188,9 @@ type Options struct {
// If set, print debug information.
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
return c.lookupMountUpdate(out, child.mountPoint)
}
if child != nil {
if child != nil && !parent.mount.options.LookupKnownChildren {
code = child.fsInode.GetAttr(out, nil, &header.Context)
} else {
child, code = parent.fsInode.Lookup(out, name, &header.Context)
......
......@@ -33,8 +33,9 @@ func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) {
AttrTimeout: testTtl,
NegativeTimeout: 0.0,
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 {
t.Fatal("NewServer", err)
}
......
......@@ -541,10 +541,17 @@ func (n *pathInode) Open(flags uint32, context *fuse.Context) (file nodefs.File,
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)
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()
*out = *fi
}
......
......@@ -99,6 +99,7 @@ func NewTestCase(t *testing.T) *testCase {
AttrTimeout: testTtl,
NegativeTimeout: 0.0,
Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
})
tc.state, err = fuse.NewServer(
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)
}
}
......@@ -25,6 +25,7 @@ var testAOpts = AutoUnionFsOptions{
AttrTimeout: entryTtl,
NegativeTimeout: 0,
Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
},
HideReadonly: true,
Version: "version",
......
......@@ -96,6 +96,7 @@ func setupUfs(t *testing.T) (wd string, cleanup func()) {
NegativeTimeout: entryTtl / 2,
PortableInodes: true,
Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
}
pathfs := pathfs.NewPathNodeFs(ufs,
......@@ -1162,6 +1163,7 @@ func TestUnionFsDisappearing(t *testing.T) {
AttrTimeout: entryTtl,
NegativeTimeout: entryTtl,
Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
}
nfs := pathfs.NewPathNodeFs(ufs, nil)
......
......@@ -64,6 +64,7 @@ func TestXAttrCaching(t *testing.T) {
AttrTimeout: entryTtl / 2,
NegativeTimeout: entryTtl / 2,
Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
}
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