// 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 // // The lock is shared: several concurrent Lookups are allowed to be // run simultaneously, while Forget is exclusive. lookupLock sync.RWMutex } // 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) }