Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
7 contributors

Users who have contributed to this file

@hanwen @rfjakob @navytux @akalin @ryanguest @myitcv @birdayz
486 lines (410 sloc) 13.3 KB
// 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"
"sync"
"time"
"unsafe"
"github.com/hanwen/go-fuse/v2/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
// This lock prevents Lookup() and Forget() from running concurrently.
// Locking at this level is a big hammer, but makes sure we don't return
// forgotten nodes to the kernel. Problems solved by this lock:
// https://github.com/hanwen/go-fuse/issues/168
// https://github.com/rfjakob/gocryptfs/issues/322
lookupLock sync.Mutex
}
// 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(&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(nil, &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 i%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)
}
// FileNotifyStoreCache notifies the kernel about changed data of the inode.
//
// This call is similar to FileNotify, but instead of only invalidating a data
// region, it puts updated data directly to the kernel cache:
//
// After this call completes, the kernel has put updated data into the inode's cache,
// and will use data from that cache for non direct-IO reads from the inode
// in corresponding data region. After kernel's cache data is evicted, the kernel
// will have to issue new Read calls on user request to get data content.
//
// ENOENT is returned if the kernel does not currently have entry for this
// inode in its dentry cache.
func (c *FileSystemConnector) FileNotifyStoreCache(node *Inode, off int64, data []byte) fuse.Status {
var nID uint64
if node == c.rootNode {
nID = fuse.FUSE_ROOT_ID
} else {
nID = c.inodeMap.Handle(&node.handled)
}
if nID == 0 {
// the kernel does not currently know about this inode.
return fuse.ENOENT
}
return c.server.InodeNotifyStoreCache(nID, off, data)
}
// FileRetrieveCache retrieves data from kernel's inode cache.
//
// This call retrieves data from kernel's inode cache @ offset and up to
// len(dest) bytes. If kernel cache has fewer consecutive data starting at
// offset, that fewer amount is returned. In particular if inode data at offset
// is not cached (0, OK) is returned.
//
// If the kernel does not currently have entry for this inode in its dentry
// cache (0, OK) is still returned, pretending that the inode could be known to
// the kernel, but kernel's inode cache is empty.
func (c *FileSystemConnector) FileRetrieveCache(node *Inode, off int64, dest []byte) (n int, st fuse.Status) {
var nID uint64
if node == c.rootNode {
nID = fuse.FUSE_ROOT_ID
} else {
nID = c.inodeMap.Handle(&node.handled)
}
if nID == 0 {
// the kernel does not currently know about this inode.
// -> we can pretend that its cache for the inode is empty.
return 0, fuse.OK
}
return c.server.InodeRetrieveCache(nID, off, dest)
}
// 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)
}
You can’t perform that action at this time.