kubelwagen/vendor/github.com/hanwen/go-fuse/fuse/nodefs/fsconnector.go
2018-03-30 11:41:24 -07:00

424 lines
11 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
// This file contains the internal logic of the
// FileSystemConnector. The functions for satisfying the raw interface
// are in fsops.go
import (
"log"
"path/filepath"
"strings"
"time"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
)
// Tests should set to true.
var paranoia = false
// FilesystemConnector translates the raw FUSE protocol (serialized
// structs of uint32/uint64) to operations on Go objects representing
// files and directories.
type FileSystemConnector struct {
debug bool
// Callbacks for talking back to the kernel.
server *fuse.Server
// Translate between uint64 handles and *Inode.
inodeMap handleMap
// The root of the FUSE file system.
rootNode *Inode
}
// NewOptions generates FUSE options that correspond to libfuse's
// defaults.
func NewOptions() *Options {
return &Options{
NegativeTimeout: 0,
AttrTimeout: time.Second,
EntryTimeout: time.Second,
Owner: fuse.CurrentOwner(),
}
}
// NewFileSystemConnector creates a FileSystemConnector with the given
// options.
func NewFileSystemConnector(root Node, opts *Options) (c *FileSystemConnector) {
c = new(FileSystemConnector)
if opts == nil {
opts = NewOptions()
}
c.inodeMap = newPortableHandleMap()
c.rootNode = newInode(true, root)
c.verify()
c.mountRoot(opts)
// FUSE does not issue a LOOKUP for 1 (obviously), but it does
// issue a forget. This lookupUpdate is to make the counts match.
c.lookupUpdate(c.rootNode)
c.debug = opts.Debug
return c
}
// Server returns the fuse.Server that talking to the kernel.
func (c *FileSystemConnector) Server() *fuse.Server {
return c.server
}
// SetDebug toggles printing of debug information. This function is
// deprecated. Set the Debug option in the Options struct instead.
func (c *FileSystemConnector) SetDebug(debug bool) {
c.debug = debug
}
// This verifies invariants of the data structure. This routine
// acquires tree locks as it walks the inode tree.
func (c *FileSystemConnector) verify() {
if !paranoia {
return
}
root := c.rootNode
root.verify(c.rootNode.mountPoint)
}
// childLookup fills entry information for a newly created child inode
func (c *rawBridge) childLookup(out *fuse.EntryOut, n *Inode, context *fuse.Context) {
n.Node().GetAttr((*fuse.Attr)(&out.Attr), nil, context)
n.mount.fillEntry(out)
out.NodeId, out.Generation = c.fsConn().lookupUpdate(n)
if out.Ino == 0 {
out.Ino = out.NodeId
}
if out.Nlink == 0 {
// With Nlink == 0, newer kernels will refuse link
// operations.
out.Nlink = 1
}
}
func (c *rawBridge) toInode(nodeid uint64) *Inode {
if nodeid == fuse.FUSE_ROOT_ID {
return c.rootNode
}
i := (*Inode)(unsafe.Pointer(c.inodeMap.Decode(nodeid)))
return i
}
// Must run outside treeLock. Returns the nodeId and generation.
func (c *FileSystemConnector) lookupUpdate(node *Inode) (id, generation uint64) {
id, generation = c.inodeMap.Register(&node.handled)
c.verify()
return
}
// forgetUpdate decrements the reference counter for "nodeID" by "forgetCount".
// Must run outside treeLock.
func (c *FileSystemConnector) forgetUpdate(nodeID uint64, forgetCount int) {
if nodeID == fuse.FUSE_ROOT_ID {
c.rootNode.Node().OnUnmount()
// We never got a lookup for root, so don't try to
// forget root.
return
}
// Prevent concurrent modification of the tree while we are processing
// the FORGET
node := (*Inode)(unsafe.Pointer(c.inodeMap.Decode(nodeID)))
node.mount.treeLock.Lock()
defer node.mount.treeLock.Unlock()
if forgotten, _ := c.inodeMap.Forget(nodeID, forgetCount); forgotten {
if len(node.children) > 0 || !node.Node().Deletable() ||
node == c.rootNode || node.mountPoint != nil {
// We cannot forget a directory that still has children as these
// would become unreachable.
return
}
// 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()
}
// TODO - try to drop children even forget was not successful.
c.verify()
}
// InodeCount returns the number of inodes registered with the kernel.
func (c *FileSystemConnector) InodeHandleCount() int {
return c.inodeMap.Count()
}
// Finds a node within the currently known inodes, returns the last
// known node and the remaining unknown path components. If parent is
// nil, start from FUSE mountpoint.
func (c *FileSystemConnector) Node(parent *Inode, fullPath string) (*Inode, []string) {
if parent == nil {
parent = c.rootNode
}
if fullPath == "" {
return parent, nil
}
sep := string(filepath.Separator)
fullPath = strings.TrimLeft(filepath.Clean(fullPath), sep)
comps := strings.Split(fullPath, sep)
node := parent
if node.mountPoint == nil {
node.mount.treeLock.RLock()
defer node.mount.treeLock.RUnlock()
}
for i, component := range comps {
if len(component) == 0 {
continue
}
if node.mountPoint != nil {
node.mount.treeLock.RLock()
defer node.mount.treeLock.RUnlock()
}
next := node.children[component]
if next == nil {
return node, comps[i:]
}
node = next
}
return node, nil
}
// Follows the path from the given parent, doing lookups as
// necessary. The path should be '/' separated without leading slash.
func (c *FileSystemConnector) LookupNode(parent *Inode, path string) *Inode {
if path == "" {
return parent
}
components := strings.Split(path, "/")
for _, r := range components {
var a fuse.Attr
// This will not affect inode ID lookup counts, which
// are only update in response to kernel requests.
var dummy fuse.InHeader
child, _ := c.internalLookup(&a, parent, r, &dummy)
if child == nil {
return nil
}
parent = child
}
return parent
}
func (c *FileSystemConnector) mountRoot(opts *Options) {
c.rootNode.mountFs(opts)
c.rootNode.mount.connector = c
c.verify()
}
// Mount() generates a synthetic directory node, and mounts the file
// system there. If opts is nil, the mount options of the root file
// system are inherited. The encompassing filesystem should pretend
// the mount point does not exist.
//
// It returns ENOENT if the directory containing the mount point does
// not exist, and EBUSY if the intended mount point already exists.
func (c *FileSystemConnector) Mount(parent *Inode, name string, root Node, opts *Options) fuse.Status {
node, code := c.lockMount(parent, name, root, opts)
if !code.Ok() {
return code
}
node.Node().OnMount(c)
return code
}
func (c *FileSystemConnector) lockMount(parent *Inode, name string, root Node, opts *Options) (*Inode, fuse.Status) {
defer c.verify()
parent.mount.treeLock.Lock()
defer parent.mount.treeLock.Unlock()
node := parent.children[name]
if node != nil {
return nil, fuse.EBUSY
}
node = newInode(true, root)
if opts == nil {
opts = c.rootNode.mountPoint.options
}
node.mountFs(opts)
node.mount.connector = c
parent.addChild(name, node)
node.mountPoint.parentInode = parent
if c.debug {
log.Printf("Mount %T on subdir %s, parent %d", node,
name, c.inodeMap.Handle(&parent.handled))
}
return node, fuse.OK
}
// Unmount() tries to unmount the given inode. It returns EINVAL if the
// path does not exist, or is not a mount point, and EBUSY if there
// are open files or submounts below this node.
func (c *FileSystemConnector) Unmount(node *Inode) fuse.Status {
// TODO - racy.
if node.mountPoint == nil {
log.Println("not a mountpoint:", c.inodeMap.Handle(&node.handled))
return fuse.EINVAL
}
nodeID := c.inodeMap.Handle(&node.handled)
// Must lock parent to update tree structure.
parentNode := node.mountPoint.parentInode
parentNode.mount.treeLock.Lock()
defer parentNode.mount.treeLock.Unlock()
mount := node.mountPoint
name := node.mountPoint.mountName()
if mount.openFiles.Count() > 0 {
return fuse.EBUSY
}
node.mount.treeLock.Lock()
defer node.mount.treeLock.Unlock()
if mount.mountInode != node {
log.Panicf("got two different mount inodes %v vs %v",
c.inodeMap.Handle(&mount.mountInode.handled),
c.inodeMap.Handle(&node.handled))
}
if !node.canUnmount() {
return fuse.EBUSY
}
delete(parentNode.children, name)
node.Node().OnUnmount()
parentId := c.inodeMap.Handle(&parentNode.handled)
if parentNode == c.rootNode {
// TODO - test coverage. Currently covered by zipfs/multizip_test.go
parentId = fuse.FUSE_ROOT_ID
}
// We have to wait until the kernel has forgotten the
// mountpoint, so the write to node.mountPoint is no longer
// racy.
mount.treeLock.Unlock()
parentNode.mount.treeLock.Unlock()
code := c.server.DeleteNotify(parentId, nodeID, name)
if code.Ok() {
delay := 100 * time.Microsecond
for {
// This operation is rare, so we kludge it to avoid
// contention.
time.Sleep(delay)
delay = delay * 2
if !c.inodeMap.Has(nodeID) {
break
}
if delay >= time.Second {
// We limit the wait at one second. If
// it takes longer, something else is
// amiss, and we would be waiting forever.
log.Println("kernel did not issue FORGET for node on Unmount.")
break
}
}
}
parentNode.mount.treeLock.Lock()
mount.treeLock.Lock()
mount.mountInode = nil
node.mountPoint = nil
return fuse.OK
}
// FileNotify notifies the kernel that data and metadata of this inode
// has changed. After this call completes, the kernel will issue a
// new GetAttr requests for metadata and new Read calls for content.
// Use negative offset for metadata-only invalidation, and zero-length
// for invalidating all content.
func (c *FileSystemConnector) FileNotify(node *Inode, off int64, length int64) fuse.Status {
var nId uint64
if node == c.rootNode {
nId = fuse.FUSE_ROOT_ID
} else {
nId = c.inodeMap.Handle(&node.handled)
}
if nId == 0 {
return fuse.OK
}
return c.server.InodeNotify(nId, off, length)
}
// EntryNotify makes the kernel forget the entry data from the given
// name from a directory. After this call, the kernel will issue a
// new lookup request for the given name when necessary. No filesystem
// related locks should be held when calling this.
func (c *FileSystemConnector) EntryNotify(node *Inode, name string) fuse.Status {
var nId uint64
if node == c.rootNode {
nId = fuse.FUSE_ROOT_ID
} else {
nId = c.inodeMap.Handle(&node.handled)
}
if nId == 0 {
return fuse.OK
}
return c.server.EntryNotify(nId, name)
}
// DeleteNotify signals to the kernel that the named entry in dir for
// the child disappeared. No filesystem related locks should be held
// when calling this.
func (c *FileSystemConnector) DeleteNotify(dir *Inode, child *Inode, name string) fuse.Status {
var nId uint64
if dir == c.rootNode {
nId = fuse.FUSE_ROOT_ID
} else {
nId = c.inodeMap.Handle(&dir.handled)
}
if nId == 0 {
return fuse.OK
}
chId := c.inodeMap.Handle(&child.handled)
return c.server.DeleteNotify(nId, chId, name)
}