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)
......
...@@ -33,8 +33,9 @@ func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) { ...@@ -33,8 +33,9 @@ func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) {
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
} }
......
...@@ -99,6 +99,7 @@ func NewTestCase(t *testing.T) *testCase { ...@@ -99,6 +99,7 @@ func NewTestCase(t *testing.T) *testCase {
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)
}
}
...@@ -25,6 +25,7 @@ var testAOpts = AutoUnionFsOptions{ ...@@ -25,6 +25,7 @@ var testAOpts = AutoUnionFsOptions{
AttrTimeout: entryTtl, AttrTimeout: entryTtl,
NegativeTimeout: 0, NegativeTimeout: 0,
Debug: testutil.VerboseTest(), Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
}, },
HideReadonly: true, HideReadonly: true,
Version: "version", Version: "version",
......
...@@ -96,6 +96,7 @@ func setupUfs(t *testing.T) (wd string, cleanup func()) { ...@@ -96,6 +96,7 @@ func setupUfs(t *testing.T) (wd string, cleanup func()) {
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,
...@@ -1162,6 +1163,7 @@ func TestUnionFsDisappearing(t *testing.T) { ...@@ -1162,6 +1163,7 @@ func TestUnionFsDisappearing(t *testing.T) {
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)
......
...@@ -64,6 +64,7 @@ func TestXAttrCaching(t *testing.T) { ...@@ -64,6 +64,7 @@ func TestXAttrCaching(t *testing.T) {
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