Commit 0107672a authored by Jakob Unterwurzacher's avatar Jakob Unterwurzacher Committed by Han-Wen Nienhuys

nodefs: move the parent tracking from pathfs into nodefs

The reason we have to do parent tracking is combination of (a) the Node
structure has a map to its children (b) FORGET messages don't enumerate
parents of a node, just the node to be forgotten.
Hence to keep the map in sync with the kernel's FS tree, we have to update
the parents of a node on FORGET too.

This passes 90+ iterations of fsstress-loopback.bash .
( https://github.com/rfjakob/gocryptfs/blob/master/stress_tests/fsstress-loopback.bash )

Change-Id: Idea478d25703a59d17086db59c110ac55281176a
parent 475f27fd
...@@ -37,14 +37,20 @@ type Node interface { ...@@ -37,14 +37,20 @@ type Node interface {
// for directory Nodes. // for directory Nodes.
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 inode may be // Deletable() should return true if this node may be discarded once
// discarded from the children list. This will be called from // the kernel forgets its reference.
// within the treeLock critical section, so you cannot look at // If it returns false, OnForget will never get called for this node. This
// other inodes. // is appropriate if the filesystem has no persistent backing store
// (in-memory filesystems) where discarding the node loses the stored data.
// Deletable will be called from within the treeLock critical section, so you
// cannot look at other nodes.
Deletable() bool Deletable() bool
// OnForget is called when the reference to this inode is // OnForget is called when the kernel forgets its reference to this node and
// dropped from the tree. // sends a FORGET request. It should perform cleanup and free memory as
// appropriate for the filesystem.
// OnForget is not called if the node is a directory and has children.
// This is called from within a treeLock critical section.
OnForget() OnForget()
// Misc. // Misc.
......
...@@ -128,14 +128,33 @@ func (c *FileSystemConnector) forgetUpdate(nodeID uint64, forgetCount int) { ...@@ -128,14 +128,33 @@ func (c *FileSystemConnector) forgetUpdate(nodeID uint64, forgetCount int) {
if forgotten, handled := c.inodeMap.Forget(nodeID, forgetCount); forgotten { if forgotten, handled := c.inodeMap.Forget(nodeID, forgetCount); forgotten {
node := (*Inode)(unsafe.Pointer(handled)) node := (*Inode)(unsafe.Pointer(handled))
// Prevent concurrent modification of the tree while we are processing
// the FORGET
//
// TODO actually the lock should be taken BEFORE running inodeMap.Forget().
// However, treeLock is per-submount, and we don't know which one to lock
// in advance. Still passes fsstress testing just fine.
node.mount.treeLock.Lock() node.mount.treeLock.Lock()
defer node.mount.treeLock.Unlock()
if len(node.children) > 0 || !node.Node().Deletable() || if len(node.children) > 0 || !node.Node().Deletable() ||
node == c.rootNode || node.mountPoint != nil { node == c.rootNode || node.mountPoint != nil {
// We cannot forget a directory that still has children as these
node.mount.treeLock.Unlock() // would become unreachable.
return return
} }
node.mount.treeLock.Unlock() // We have to remove ourself from all parents.
// Create a copy of node.parents so we can safely iterate over it
// while modifying the original.
parents := make(map[parentData]struct{}, len(node.parents))
for k, v := range node.parents {
parents[k] = v
}
for p := range parents {
// This also modifies node.parents
p.parent.rmChild(p.name)
}
node.fsInode.OnForget() node.fsInode.OnForget()
} }
......
...@@ -27,8 +27,9 @@ type fileSystemMount struct { ...@@ -27,8 +27,9 @@ type fileSystemMount struct {
// Options for the mount. // Options for the mount.
options *Options options *Options
// Protects Children hashmaps within the mount. treeLock // Protects the "children" and "parents" hashmaps of the inodes
// should be acquired before openFilesLock. // within the mount.
// treeLock should be acquired before openFilesLock.
// //
// If multiple treeLocks must be acquired, the treeLocks // If multiple treeLocks must be acquired, the treeLocks
// closer to the root must be acquired first. // closer to the root must be acquired first.
......
...@@ -7,6 +7,11 @@ import ( ...@@ -7,6 +7,11 @@ import (
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse"
) )
type parentData struct {
parent *Inode
name string
}
// An Inode reflects the kernel's idea of the inode. Inodes have IDs // An Inode reflects the kernel's idea of the inode. Inodes have IDs
// that are communicated to the kernel, and they have a tree // that are communicated to the kernel, and they have a tree
// structure: a directory Inode may contain named children. Each // structure: a directory Inode may contain named children. Each
...@@ -32,6 +37,8 @@ type Inode struct { ...@@ -32,6 +37,8 @@ type Inode struct {
// All data below is protected by treeLock. // All data below is protected by treeLock.
children map[string]*Inode children map[string]*Inode
// Due to hard links, an Inode can have many parents.
parents map[parentData]struct{}
// Non-nil if this inode is a mountpoint, ie. the Root of a // Non-nil if this inode is a mountpoint, ie. the Root of a
// NodeFileSystem. // NodeFileSystem.
...@@ -40,6 +47,7 @@ type Inode struct { ...@@ -40,6 +47,7 @@ type Inode struct {
func newInode(isDir bool, fsNode Node) *Inode { func newInode(isDir bool, fsNode Node) *Inode {
me := new(Inode) me := new(Inode)
me.parents = map[parentData]struct{}{}
if isDir { if isDir {
me.children = make(map[string]*Inode, initDirSize) me.children = make(map[string]*Inode, initDirSize)
} }
...@@ -75,6 +83,19 @@ func (n *Inode) Children() (out map[string]*Inode) { ...@@ -75,6 +83,19 @@ func (n *Inode) Children() (out map[string]*Inode) {
return out return out
} }
// Parent returns a random parent and the name this inode has under this parent.
// This function can be used to walk up the directory tree. It will not cross
// sub-mounts.
func (n *Inode) Parent() (parent *Inode, name string) {
if n.mountPoint != nil {
return nil, ""
}
for k := range n.parents {
return k.parent, k.name
}
return nil, ""
}
// FsChildren returns all the children from the same filesystem. It // FsChildren returns all the children from the same filesystem. It
// will skip mountpoints. // will skip mountpoints.
func (n *Inode) FsChildren() (out map[string]*Inode) { func (n *Inode) FsChildren() (out map[string]*Inode) {
...@@ -144,7 +165,7 @@ func (n *Inode) AddChild(name string, child *Inode) { ...@@ -144,7 +165,7 @@ func (n *Inode) AddChild(name string, child *Inode) {
// TreeWatcher is an additional interface that Nodes can implement. // TreeWatcher is an additional interface that Nodes can implement.
// If they do, the OnAdd and OnRemove are called for operations on the // If they do, the OnAdd and OnRemove are called for operations on the
// file system tree. The functions run under a lock, so they should // file system tree. These functions run under a lock, so they should
// not do blocking operations. // not do blocking operations.
type TreeWatcher interface { type TreeWatcher interface {
OnAdd(parent *Inode, name string) OnAdd(parent *Inode, name string)
...@@ -173,17 +194,20 @@ func (n *Inode) addChild(name string, child *Inode) { ...@@ -173,17 +194,20 @@ func (n *Inode) addChild(name string, child *Inode) {
} }
} }
n.children[name] = child n.children[name] = child
child.parents[parentData{n, name}] = struct{}{}
if w, ok := child.Node().(TreeWatcher); ok && child.mountPoint == nil { if w, ok := child.Node().(TreeWatcher); ok && child.mountPoint == nil {
w.OnAdd(n, name) w.OnAdd(n, name)
} }
} }
// rmChild drops "name" from our children. // rmChild throws out child "name". This means (1) deleting "name" from our
// "children" map and (2) deleting ourself from the child's "parents" map.
// Must be called with treeLock for the mount held. // Must be called with treeLock for the mount held.
func (n *Inode) rmChild(name string) *Inode { func (n *Inode) rmChild(name string) *Inode {
ch := n.children[name] ch := n.children[name]
if ch != nil { if ch != nil {
delete(n.children, name) delete(n.children, name)
delete(ch.parents, parentData{n, name})
if w, ok := ch.Node().(TreeWatcher); ok && ch.mountPoint == nil { if w, ok := ch.Node().(TreeWatcher); ok && ch.mountPoint == nil {
w.OnRemove(n, name) w.OnRemove(n, name)
} }
......
...@@ -12,11 +12,11 @@ import ( ...@@ -12,11 +12,11 @@ import (
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/fuse/nodefs"
) )
// A parent pointer: node should be reachable as parent.children[name] // refCountedInode is used in clientInodeMap. The reference count is used to decide
type clientInodePath struct { // if the entry in clientInodeMap can be dropped.
parent *pathInode type refCountedInode struct {
name string node *pathInode
node *pathInode refCount int
} }
// PathNodeFs is the file system that can translate an inode back to a // PathNodeFs is the file system that can translate an inode back to a
...@@ -32,12 +32,11 @@ type PathNodeFs struct { ...@@ -32,12 +32,11 @@ type PathNodeFs struct {
root *pathInode root *pathInode
connector *nodefs.FileSystemConnector connector *nodefs.FileSystemConnector
// protects pathInode.parents // protects clientInodeMap
pathLock sync.RWMutex pathLock sync.RWMutex
// This map lists all the parent links known for a given // This map lists all the parent links known for a given inode number.
// nodeId. clientInodeMap map[uint64]*refCountedInode
clientInodeMap map[uint64][]*clientInodePath
options *PathNodeFsOptions options *PathNodeFsOptions
} }
...@@ -68,7 +67,7 @@ func (fs *PathNodeFs) ForgetClientInodes() { ...@@ -68,7 +67,7 @@ func (fs *PathNodeFs) ForgetClientInodes() {
return return
} }
fs.pathLock.Lock() fs.pathLock.Lock()
fs.clientInodeMap = map[uint64][]*clientInodePath{} fs.clientInodeMap = map[uint64]*refCountedInode{}
fs.root.forgetClientInodes() fs.root.forgetClientInodes()
fs.pathLock.Unlock() fs.pathLock.Unlock()
} }
...@@ -188,9 +187,7 @@ func (fs *PathNodeFs) AllFiles(name string, mask uint32) []nodefs.WithFlags { ...@@ -188,9 +187,7 @@ func (fs *PathNodeFs) AllFiles(name string, mask uint32) []nodefs.WithFlags {
// NewPathNodeFs returns a file system that translates from inodes to // NewPathNodeFs returns a file system that translates from inodes to
// path names. // path names.
func NewPathNodeFs(fs FileSystem, opts *PathNodeFsOptions) *PathNodeFs { func NewPathNodeFs(fs FileSystem, opts *PathNodeFsOptions) *PathNodeFs {
root := &pathInode{ root := &pathInode{}
parents: map[parentData]struct{}{},
}
root.fs = fs root.fs = fs
if opts == nil { if opts == nil {
...@@ -200,7 +197,7 @@ func NewPathNodeFs(fs FileSystem, opts *PathNodeFsOptions) *PathNodeFs { ...@@ -200,7 +197,7 @@ func NewPathNodeFs(fs FileSystem, opts *PathNodeFsOptions) *PathNodeFs {
pfs := &PathNodeFs{ pfs := &PathNodeFs{
fs: fs, fs: fs,
root: root, root: root,
clientInodeMap: map[uint64][]*clientInodePath{}, clientInodeMap: map[uint64]*refCountedInode{},
options: opts, options: opts,
} }
root.pathFs = pfs root.pathFs = pfs
...@@ -212,11 +209,6 @@ func (fs *PathNodeFs) Root() nodefs.Node { ...@@ -212,11 +209,6 @@ func (fs *PathNodeFs) Root() nodefs.Node {
return fs.root return fs.root
} }
type parentData struct {
parent *pathInode
name string
}
// This is a combination of dentry (entry in the file/directory and // This is a combination of dentry (entry in the file/directory and
// the inode). This structure is used to implement glue for FSes where // the inode). This structure is used to implement glue for FSes where
// there is a one-to-one mapping of paths and inodes. // there is a one-to-one mapping of paths and inodes.
...@@ -224,8 +216,6 @@ type pathInode struct { ...@@ -224,8 +216,6 @@ type pathInode struct {
pathFs *PathNodeFs pathFs *PathNodeFs
fs FileSystem fs FileSystem
parents map[parentData]struct{}
// This is to correctly resolve hardlinks of the underlying // This is to correctly resolve hardlinks of the underlying
// real filesystem. // real filesystem.
clientInode uint64 clientInode uint64
...@@ -256,14 +246,6 @@ func (n *pathInode) Inode() *nodefs.Inode { ...@@ -256,14 +246,6 @@ func (n *pathInode) Inode() *nodefs.Inode {
return n.inode return n.inode
} }
// TODO - return *parentData?
func (n *pathInode) parent() parentData {
for k := range n.parents {
return k
}
return parentData{}
}
func (n *pathInode) SetInode(node *nodefs.Inode) { func (n *pathInode) SetInode(node *nodefs.Inode) {
n.inode = node n.inode = node
} }
...@@ -291,24 +273,24 @@ func (n *pathInode) GetPath() string { ...@@ -291,24 +273,24 @@ func (n *pathInode) GetPath() string {
// effort to avoid allocations. // effort to avoid allocations.
n.pathFs.pathLock.RLock() n.pathFs.pathLock.RLock()
walkUp := n walkUp := n.Inode()
// TODO - guess depth? use *parentData? // TODO - guess depth?
parents := make([]parentData, 0, 10) segments := make([]string, 0, 10)
for { for {
p := walkUp.parent() parent, name := walkUp.Parent()
if p.parent == nil { if parent == nil {
break break
} }
parents = append(parents, p) segments = append(segments, name)
pathLen += len(p.name) + 1 pathLen += len(name) + 1
walkUp = p.parent walkUp = parent
} }
pathLen-- pathLen--
pathBytes := make([]byte, 0, pathLen) pathBytes := make([]byte, 0, pathLen)
for i := len(parents) - 1; i >= 0; i-- { for i := len(segments) - 1; i >= 0; i-- {
pathBytes = append(pathBytes, parents[i].name...) pathBytes = append(pathBytes, segments[i]...)
if i > 0 { if i > 0 {
pathBytes = append(pathBytes, '/') pathBytes = append(pathBytes, '/')
} }
...@@ -320,7 +302,7 @@ func (n *pathInode) GetPath() string { ...@@ -320,7 +302,7 @@ func (n *pathInode) GetPath() string {
log.Printf("Inode = %q (%s)", path, n.fs.String()) log.Printf("Inode = %q (%s)", path, n.fs.String())
} }
if walkUp != n.pathFs.root { if walkUp != n.pathFs.root.Inode() {
// This might happen if the node has been removed from // This might happen if the node has been removed from
// the tree using unlink, but we are forced to run // the tree using unlink, but we are forced to run
// some file system operation, because the file is // some file system operation, because the file is
...@@ -334,20 +316,9 @@ func (n *pathInode) GetPath() string { ...@@ -334,20 +316,9 @@ func (n *pathInode) GetPath() string {
} }
func (n *pathInode) OnAdd(parent *nodefs.Inode, name string) { func (n *pathInode) OnAdd(parent *nodefs.Inode, name string) {
n.pathFs.pathLock.Lock() // TODO it would be logical to increment the clientInodeMap reference count
defer n.pathFs.pathLock.Unlock() // here. However, as the inode number is loaded lazily, we cannot do it
// yet.
pathParent := parent.Node().(*pathInode)
n.parents[parentData{pathParent, name}] = struct{}{}
if n.clientInode > 0 && n.pathFs.options.ClientInodes {
m := n.pathFs.clientInodeMap[n.clientInode]
e := &clientInodePath{
pathParent, name, n,
}
m = append(m, e)
n.pathFs.clientInodeMap[n.clientInode] = m
}
} }
func (n *pathInode) rmChild(name string) *pathInode { func (n *pathInode) rmChild(name string) *pathInode {
...@@ -359,79 +330,40 @@ func (n *pathInode) rmChild(name string) *pathInode { ...@@ -359,79 +330,40 @@ func (n *pathInode) rmChild(name string) *pathInode {
} }
func (n *pathInode) OnRemove(parent *nodefs.Inode, name string) { func (n *pathInode) OnRemove(parent *nodefs.Inode, name string) {
n.pathFs.pathLock.Lock() if n.clientInode == 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() {
defer n.pathFs.pathLock.Unlock() return
}
// TODO - paranoia: what if the cast fails? Can this happen?
parentPI := parent.Node().(*pathInode)
delete(n.parents, parentData{parentPI, name})
if n.clientInode > 0 && n.pathFs.options.ClientInodes {
m := n.pathFs.clientInodeMap[n.clientInode]
idx := -1 n.pathFs.pathLock.Lock()
// Find the right entry: both "parent" and "name" must match r := n.pathFs.clientInodeMap[n.clientInode]
for i, v := range m { if r != nil {
if v.parent == parentPI && v.name == name { r.refCount--
idx = i if r.refCount == 0 {
break
}
}
if idx >= 0 {
// Delete the "idx" entry from the middle of the slice by moving the
// last element over it and truncating the slice
m[idx] = m[len(m)-1]
m = m[:len(m)-1]
n.pathFs.clientInodeMap[n.clientInode] = m
}
if len(m) == 0 {
delete(n.pathFs.clientInodeMap, n.clientInode) delete(n.pathFs.clientInodeMap, n.clientInode)
} }
} }
n.pathFs.pathLock.Unlock()
} }
// Handle a change in clientInode number for an other wise unchanged // setClientInode sets the inode number if has not been set yet.
// pathInode. // This function exists to allow lazy-loading of the inode number.
func (n *pathInode) setClientInode(ino uint64) { func (n *pathInode) setClientInode(ino uint64) {
if ino == n.clientInode || !n.pathFs.options.ClientInodes { if ino == 0 || n.clientInode != 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() {
return return
} }
n.pathFs.pathLock.Lock() n.pathFs.pathLock.Lock()
defer n.pathFs.pathLock.Unlock() defer n.pathFs.pathLock.Unlock()
if n.clientInode != 0 {
delete(n.pathFs.clientInodeMap, n.clientInode)
}
n.clientInode = ino n.clientInode = ino
if p := n.parent(); p.parent != nil { n.pathFs.clientInodeMap[ino] = &refCountedInode{node: n, refCount: 1}
e := &clientInodePath{
p.parent, p.name, n,
}
n.pathFs.clientInodeMap[ino] = append(n.pathFs.clientInodeMap[ino], e)
}
} }
func (n *pathInode) OnForget() { func (n *pathInode) OnForget() {
if n.clientInode == 0 || !n.pathFs.options.ClientInodes { if n.clientInode == 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() {
return return
} }
n.pathFs.pathLock.Lock() n.pathFs.pathLock.Lock()
parents := make(map[parentData]struct{}, len(n.parents)) delete(n.pathFs.clientInodeMap, n.clientInode)
for k, v := range n.parents {
parents[k] = v
}
n.pathFs.pathLock.Unlock() n.pathFs.pathLock.Unlock()
// Remove ourself from all parents. This will also remove the entry from
// clientInodeMap when the last parent is removed.
for p := range parents {
if ch := p.parent.Inode().GetChild(p.name); ch != nil && ch.Node() != n {
// Another node has taken our place - leave it alone.
continue
}
p.parent.Inode().RmChild(p.name)
}
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
...@@ -533,6 +465,8 @@ func (n *pathInode) Rename(oldName string, newParent nodefs.Node, newName string ...@@ -533,6 +465,8 @@ func (n *pathInode) Rename(oldName string, newParent nodefs.Node, newName string
newPath := filepath.Join(p.GetPath(), newName) newPath := filepath.Join(p.GetPath(), newName)
code = n.fs.Rename(oldPath, newPath, context) code = n.fs.Rename(oldPath, newPath, context)
if code.Ok() { if code.Ok() {
// The rename may have overwritten another file, remove it from the tree
p.Inode().RmChild(newName)
ch := n.Inode().RmChild(oldName) ch := n.Inode().RmChild(oldName)
if ch != nil { if ch != nil {
// oldName may have been forgotten in the meantime. // oldName may have been forgotten in the meantime.
...@@ -584,13 +518,12 @@ func (n *pathInode) Create(name string, flags uint32, mode uint32, context *fuse ...@@ -584,13 +518,12 @@ func (n *pathInode) Create(name string, flags uint32, mode uint32, context *fuse
func (n *pathInode) createChild(name string, isDir bool) *pathInode { func (n *pathInode) createChild(name string, isDir bool) *pathInode {
i := &pathInode{ i := &pathInode{
parents: map[parentData]struct{}{}, fs: n.fs,
pathFs: n.pathFs,
} }
i.fs = n.fs
i.pathFs = n.pathFs
n.Inode().NewChild(name, isDir, i) n.Inode().NewChild(name, isDir, i)
return i return i
} }
...@@ -620,12 +553,12 @@ func (n *pathInode) Lookup(out *fuse.Attr, name string, context *fuse.Context) ( ...@@ -620,12 +553,12 @@ func (n *pathInode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (
func (n *pathInode) findChild(fi *fuse.Attr, name string, fullPath string) (out *pathInode) { func (n *pathInode) findChild(fi *fuse.Attr, name string, fullPath string) (out *pathInode) {
if fi.Ino > 0 { if fi.Ino > 0 {
n.pathFs.pathLock.RLock() n.pathFs.pathLock.RLock()
v := n.pathFs.clientInodeMap[fi.Ino] r := n.pathFs.clientInodeMap[fi.Ino]
if len(v) > 0 { if r != nil {
out = v[0].node out = r.node
r.refCount++
if fi.Nlink == 1 { if fi.Nlink == 1 {
log.Println("Found linked inode, but Nlink == 1, ino=%d", fullPath, fi.Ino) log.Printf("Found linked inode, but Nlink == 1, ino=%d, fullPath=%q", fi.Ino, fullPath)
} }
} }
n.pathFs.pathLock.RUnlock() n.pathFs.pathLock.RUnlock()
...@@ -633,7 +566,7 @@ func (n *pathInode) findChild(fi *fuse.Attr, name string, fullPath string) (out ...@@ -633,7 +566,7 @@ func (n *pathInode) findChild(fi *fuse.Attr, name string, fullPath string) (out
if out == nil { if out == nil {
out = n.createChild(name, fi.IsDir()) out = n.createChild(name, fi.IsDir())
out.clientInode = fi.Ino out.setClientInode(fi.Ino)
} else { } else {
n.Inode().AddChild(name, out.Inode()) n.Inode().AddChild(name, out.Inode())
} }
......
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