424 lines
11 KiB
Go
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)
|
|
}
|