287 lines
6.9 KiB
Go
287 lines
6.9 KiB
Go
// Copyright 2016 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 nodefs
|
|
|
|
import (
|
|
"log"
|
|
"sync"
|
|
|
|
"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
|
|
// that are communicated to the kernel, and they have a tree
|
|
// structure: a directory Inode may contain named children. Each
|
|
// Inode object is paired with a Node object, which file system
|
|
// implementers should supply.
|
|
type Inode struct {
|
|
handled handled
|
|
|
|
// Number of open files and its protection.
|
|
openFilesMutex sync.Mutex
|
|
openFiles []*openedFile
|
|
|
|
fsInode Node
|
|
|
|
// Each inode belongs to exactly one fileSystemMount. This
|
|
// pointer is constant during the lifetime, except upon
|
|
// Unmount() when it is set to nil.
|
|
mount *fileSystemMount
|
|
|
|
// All data below is protected by treeLock.
|
|
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
|
|
// NodeFileSystem.
|
|
mountPoint *fileSystemMount
|
|
}
|
|
|
|
func newInode(isDir bool, fsNode Node) *Inode {
|
|
me := new(Inode)
|
|
me.parents = map[parentData]struct{}{}
|
|
if isDir {
|
|
me.children = make(map[string]*Inode, initDirSize)
|
|
}
|
|
me.fsInode = fsNode
|
|
me.fsInode.SetInode(me)
|
|
return me
|
|
}
|
|
|
|
// public methods.
|
|
|
|
// Returns any open file, preferably a r/w one.
|
|
func (n *Inode) AnyFile() (file File) {
|
|
n.openFilesMutex.Lock()
|
|
for _, f := range n.openFiles {
|
|
if file == nil || f.WithFlags.OpenFlags&fuse.O_ANYWRITE != 0 {
|
|
file = f.WithFlags.File
|
|
}
|
|
}
|
|
n.openFilesMutex.Unlock()
|
|
|
|
return file
|
|
}
|
|
|
|
// Children returns all children of this inode.
|
|
func (n *Inode) Children() (out map[string]*Inode) {
|
|
n.mount.treeLock.RLock()
|
|
out = make(map[string]*Inode, len(n.children))
|
|
for k, v := range n.children {
|
|
out[k] = v
|
|
}
|
|
n.mount.treeLock.RUnlock()
|
|
|
|
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, ""
|
|
}
|
|
n.mount.treeLock.RLock()
|
|
defer n.mount.treeLock.RUnlock()
|
|
for k := range n.parents {
|
|
return k.parent, k.name
|
|
}
|
|
return nil, ""
|
|
}
|
|
|
|
// FsChildren returns all the children from the same filesystem. It
|
|
// will skip mountpoints.
|
|
func (n *Inode) FsChildren() (out map[string]*Inode) {
|
|
n.mount.treeLock.RLock()
|
|
out = map[string]*Inode{}
|
|
for k, v := range n.children {
|
|
if v.mount == n.mount {
|
|
out[k] = v
|
|
}
|
|
}
|
|
n.mount.treeLock.RUnlock()
|
|
|
|
return out
|
|
}
|
|
|
|
// Node returns the file-system specific node.
|
|
func (n *Inode) Node() Node {
|
|
return n.fsInode
|
|
}
|
|
|
|
// Files() returns an opens file that have bits in common with the
|
|
// give mask. Use mask==0 to return all files.
|
|
func (n *Inode) Files(mask uint32) (files []WithFlags) {
|
|
n.openFilesMutex.Lock()
|
|
for _, f := range n.openFiles {
|
|
if mask == 0 || f.WithFlags.OpenFlags&mask != 0 {
|
|
files = append(files, f.WithFlags)
|
|
}
|
|
}
|
|
n.openFilesMutex.Unlock()
|
|
return files
|
|
}
|
|
|
|
// IsDir returns true if this is a directory.
|
|
func (n *Inode) IsDir() bool {
|
|
return n.children != nil
|
|
}
|
|
|
|
// NewChild adds a new child inode to this inode.
|
|
func (n *Inode) NewChild(name string, isDir bool, fsi Node) *Inode {
|
|
ch := newInode(isDir, fsi)
|
|
ch.mount = n.mount
|
|
n.AddChild(name, ch)
|
|
return ch
|
|
}
|
|
|
|
// GetChild returns a child inode with the given name, or nil if it
|
|
// does not exist.
|
|
func (n *Inode) GetChild(name string) (child *Inode) {
|
|
n.mount.treeLock.RLock()
|
|
child = n.children[name]
|
|
n.mount.treeLock.RUnlock()
|
|
|
|
return child
|
|
}
|
|
|
|
// AddChild adds a child inode. The parent inode must be a directory
|
|
// node.
|
|
func (n *Inode) AddChild(name string, child *Inode) {
|
|
if child == nil {
|
|
log.Panicf("adding nil child as %q", name)
|
|
}
|
|
n.mount.treeLock.Lock()
|
|
n.addChild(name, child)
|
|
n.mount.treeLock.Unlock()
|
|
}
|
|
|
|
// TreeWatcher is an additional interface that Nodes can implement.
|
|
// If they do, the OnAdd and OnRemove are called for operations on the
|
|
// file system tree. These functions run under a lock, so they should
|
|
// not do blocking operations.
|
|
type TreeWatcher interface {
|
|
OnAdd(parent *Inode, name string)
|
|
OnRemove(parent *Inode, name string)
|
|
}
|
|
|
|
// RmChild removes an inode by name, and returns it. It returns nil if
|
|
// child does not exist.
|
|
func (n *Inode) RmChild(name string) (ch *Inode) {
|
|
n.mount.treeLock.Lock()
|
|
ch = n.rmChild(name)
|
|
n.mount.treeLock.Unlock()
|
|
return
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// private
|
|
|
|
// addChild adds "child" to our children under name "name".
|
|
// Must be called with treeLock for the mount held.
|
|
func (n *Inode) addChild(name string, child *Inode) {
|
|
if paranoia {
|
|
ch := n.children[name]
|
|
if ch != nil {
|
|
log.Panicf("Already have an Inode with same name: %v: %v", name, ch)
|
|
}
|
|
}
|
|
n.children[name] = child
|
|
child.parents[parentData{n, name}] = struct{}{}
|
|
if w, ok := child.Node().(TreeWatcher); ok && child.mountPoint == nil {
|
|
w.OnAdd(n, name)
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
func (n *Inode) rmChild(name string) *Inode {
|
|
ch := n.children[name]
|
|
if ch != nil {
|
|
delete(n.children, name)
|
|
delete(ch.parents, parentData{n, name})
|
|
if w, ok := ch.Node().(TreeWatcher); ok && ch.mountPoint == nil {
|
|
w.OnRemove(n, name)
|
|
}
|
|
}
|
|
return ch
|
|
}
|
|
|
|
// Can only be called on untouched root inodes.
|
|
func (n *Inode) mountFs(opts *Options) {
|
|
n.mountPoint = &fileSystemMount{
|
|
openFiles: newPortableHandleMap(),
|
|
mountInode: n,
|
|
options: opts,
|
|
}
|
|
n.mount = n.mountPoint
|
|
}
|
|
|
|
// Must be called with treeLock held.
|
|
func (n *Inode) canUnmount() bool {
|
|
for _, v := range n.children {
|
|
if v.mountPoint != nil {
|
|
// This access may be out of date, but it is no
|
|
// problem to err on the safe side.
|
|
return false
|
|
}
|
|
if !v.canUnmount() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
n.openFilesMutex.Lock()
|
|
ok := len(n.openFiles) == 0
|
|
n.openFilesMutex.Unlock()
|
|
return ok
|
|
}
|
|
|
|
func (n *Inode) getMountDirEntries() (out []fuse.DirEntry) {
|
|
n.mount.treeLock.RLock()
|
|
for k, v := range n.children {
|
|
if v.mountPoint != nil {
|
|
out = append(out, fuse.DirEntry{
|
|
Name: k,
|
|
Mode: fuse.S_IFDIR,
|
|
})
|
|
}
|
|
}
|
|
n.mount.treeLock.RUnlock()
|
|
|
|
return out
|
|
}
|
|
|
|
const initDirSize = 20
|
|
|
|
func (n *Inode) verify(cur *fileSystemMount) {
|
|
n.handled.verify()
|
|
if n.mountPoint != nil {
|
|
if n != n.mountPoint.mountInode {
|
|
log.Panicf("mountpoint mismatch %v %v", n, n.mountPoint.mountInode)
|
|
}
|
|
cur = n.mountPoint
|
|
|
|
cur.treeLock.Lock()
|
|
defer cur.treeLock.Unlock()
|
|
}
|
|
if n.mount != cur {
|
|
log.Panicf("n.mount not set correctly %v %v", n.mount, cur)
|
|
}
|
|
|
|
for nm, ch := range n.children {
|
|
if ch == nil {
|
|
log.Panicf("Found nil child: %q", nm)
|
|
}
|
|
ch.verify(cur)
|
|
}
|
|
}
|