// 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 pathfs import ( "fmt" "log" "path/filepath" "strings" "sync" "time" "github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse/nodefs" ) // refCountedInode is used in clientInodeMap. The reference count is used to decide // if the entry in clientInodeMap can be dropped. type refCountedInode struct { node *pathInode refCount int } // PathNodeFs is the file system that can translate an inode back to a // path. The path name is then used to call into an object that has // the FileSystem interface. // // Lookups (ie. FileSystem.GetAttr) may return a inode number in its // return value. The inode number ("clientInode") is used to indicate // linked files. type PathNodeFs struct { debug bool fs FileSystem root *pathInode connector *nodefs.FileSystemConnector // protects clientInodeMap pathLock sync.RWMutex // This map lists all the parent links known for a given inode number. clientInodeMap map[uint64]*refCountedInode options *PathNodeFsOptions } // SetDebug toggles debug information: it will log path names for // each operation processed. func (fs *PathNodeFs) SetDebug(dbg bool) { fs.debug = dbg } // Mount mounts a another node filesystem with the given root on the // path. The last component of the path should not exist yet. func (fs *PathNodeFs) Mount(path string, root nodefs.Node, opts *nodefs.Options) fuse.Status { dir, name := filepath.Split(path) if dir != "" { dir = filepath.Clean(dir) } parent := fs.LookupNode(dir) if parent == nil { return fuse.ENOENT } return fs.connector.Mount(parent, name, root, opts) } // ForgetClientInodes forgets all known information on client inodes. func (fs *PathNodeFs) ForgetClientInodes() { if !fs.options.ClientInodes { return } fs.pathLock.Lock() fs.clientInodeMap = map[uint64]*refCountedInode{} fs.root.forgetClientInodes() fs.pathLock.Unlock() } // Rereads all inode numbers for all known files. func (fs *PathNodeFs) RereadClientInodes() { if !fs.options.ClientInodes { return } fs.ForgetClientInodes() fs.root.updateClientInodes() } // UnmountNode unmounts the node filesystem with the given root. func (fs *PathNodeFs) UnmountNode(node *nodefs.Inode) fuse.Status { return fs.connector.Unmount(node) } // UnmountNode unmounts the node filesystem with the given root. func (fs *PathNodeFs) Unmount(path string) fuse.Status { node := fs.Node(path) if node == nil { return fuse.ENOENT } return fs.connector.Unmount(node) } // String returns a name for this file system func (fs *PathNodeFs) String() string { name := fs.fs.String() if name == "defaultFileSystem" { name = fmt.Sprintf("%T", fs.fs) name = strings.TrimLeft(name, "*") } return name } // Connector returns the FileSystemConnector (the bridge to the raw // protocol) for this PathNodeFs. func (fs *PathNodeFs) Connector() *nodefs.FileSystemConnector { return fs.connector } // Node looks up the Inode that corresponds to the given path name, or // returns nil if not found. func (fs *PathNodeFs) Node(name string) *nodefs.Inode { n, rest := fs.LastNode(name) if len(rest) > 0 { return nil } return n } // Like Node, but use Lookup to discover inodes we may not have yet. func (fs *PathNodeFs) LookupNode(name string) *nodefs.Inode { return fs.connector.LookupNode(fs.Root().Inode(), name) } // Path constructs a path for the given Inode. If the file system // implements hard links through client-inode numbers, the path may // not be unique. func (fs *PathNodeFs) Path(node *nodefs.Inode) string { pNode := node.Node().(*pathInode) return pNode.GetPath() } // LastNode finds the deepest inode known corresponding to a path. The // unknown part of the filename is also returned. func (fs *PathNodeFs) LastNode(name string) (*nodefs.Inode, []string) { return fs.connector.Node(fs.Root().Inode(), name) } // FileNotify notifies that file contents were changed within the // given range. Use negative offset for metadata-only invalidation, // and zero-length for invalidating all content. func (fs *PathNodeFs) FileNotify(path string, off int64, length int64) fuse.Status { node, r := fs.connector.Node(fs.root.Inode(), path) if len(r) > 0 { return fuse.ENOENT } return fs.connector.FileNotify(node, 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. func (fs *PathNodeFs) EntryNotify(dir string, name string) fuse.Status { node, rest := fs.connector.Node(fs.root.Inode(), dir) if len(rest) > 0 { return fuse.ENOENT } return fs.connector.EntryNotify(node, name) } // Notify ensures that the path name is invalidates: if the inode is // known, it issues an file content Notify, if not, an entry notify // for the path is issued. The latter will clear out non-existence // cache entries. func (fs *PathNodeFs) Notify(path string) fuse.Status { node, rest := fs.connector.Node(fs.root.Inode(), path) if len(rest) > 0 { return fs.connector.EntryNotify(node, rest[0]) } return fs.connector.FileNotify(node, 0, 0) } // AllFiles returns all open files for the inode corresponding with // the given mask. func (fs *PathNodeFs) AllFiles(name string, mask uint32) []nodefs.WithFlags { n := fs.Node(name) if n == nil { return nil } return n.Files(mask) } // NewPathNodeFs returns a file system that translates from inodes to // path names. func NewPathNodeFs(fs FileSystem, opts *PathNodeFsOptions) *PathNodeFs { root := &pathInode{} root.fs = fs if opts == nil { opts = &PathNodeFsOptions{} } pfs := &PathNodeFs{ fs: fs, root: root, clientInodeMap: map[uint64]*refCountedInode{}, options: opts, } root.pathFs = pfs return pfs } // Root returns the root node for the path filesystem. func (fs *PathNodeFs) Root() nodefs.Node { return fs.root } // This is a combination of dentry (entry in the file/directory and // the inode). This structure is used to implement glue for FSes where // there is a one-to-one mapping of paths and inodes. type pathInode struct { pathFs *PathNodeFs fs FileSystem // This is to correctly resolve hardlinks of the underlying // real filesystem. clientInode uint64 inode *nodefs.Inode } func (n *pathInode) OnMount(conn *nodefs.FileSystemConnector) { n.pathFs.connector = conn n.pathFs.fs.OnMount(n.pathFs) } func (n *pathInode) OnUnmount() { } // Drop all known client inodes. Must have the treeLock. func (n *pathInode) forgetClientInodes() { n.clientInode = 0 for _, ch := range n.Inode().FsChildren() { ch.Node().(*pathInode).forgetClientInodes() } } func (fs *pathInode) Deletable() bool { return true } func (n *pathInode) Inode() *nodefs.Inode { return n.inode } func (n *pathInode) SetInode(node *nodefs.Inode) { n.inode = node } // Reread all client nodes below this node. Must run outside the treeLock. func (n *pathInode) updateClientInodes() { n.GetAttr(&fuse.Attr{}, nil, nil) for _, ch := range n.Inode().FsChildren() { ch.Node().(*pathInode).updateClientInodes() } } // GetPath returns the path relative to the mount governing this // inode. It returns nil for mount if the file was deleted or the // filesystem unmounted. func (n *pathInode) GetPath() string { if n == n.pathFs.root { return "" } pathLen := 1 // The simple solution is to collect names, and reverse join // them, them, but since this is a hot path, we take some // effort to avoid allocations. walkUp := n.Inode() // TODO - guess depth? segments := make([]string, 0, 10) for { parent, name := walkUp.Parent() if parent == nil { break } segments = append(segments, name) pathLen += len(name) + 1 walkUp = parent } pathLen-- pathBytes := make([]byte, 0, pathLen) for i := len(segments) - 1; i >= 0; i-- { pathBytes = append(pathBytes, segments[i]...) if i > 0 { pathBytes = append(pathBytes, '/') } } path := string(pathBytes) if n.pathFs.debug { log.Printf("Inode = %q (%s)", path, n.fs.String()) } if walkUp != n.pathFs.root.Inode() { // This might happen if the node has been removed from // the tree using unlink, but we are forced to run // some file system operation, because the file is // still opened. // TODO - add a deterministic disambiguating suffix. return ".deleted" } return path } func (n *pathInode) OnAdd(parent *nodefs.Inode, name string) { // TODO it would be logical to increment the clientInodeMap reference count // here. However, as the inode number is loaded lazily, we cannot do it // yet. } func (n *pathInode) rmChild(name string) *pathInode { childInode := n.Inode().RmChild(name) if childInode == nil { return nil } return childInode.Node().(*pathInode) } func (n *pathInode) OnRemove(parent *nodefs.Inode, name string) { if n.clientInode == 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() { return } n.pathFs.pathLock.Lock() r := n.pathFs.clientInodeMap[n.clientInode] if r != nil { r.refCount-- if r.refCount == 0 { delete(n.pathFs.clientInodeMap, n.clientInode) } } n.pathFs.pathLock.Unlock() } // setClientInode sets the inode number if has not been set yet. // This function exists to allow lazy-loading of the inode number. func (n *pathInode) setClientInode(ino uint64) { if ino == 0 || n.clientInode != 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() { return } n.pathFs.pathLock.Lock() defer n.pathFs.pathLock.Unlock() n.clientInode = ino n.pathFs.clientInodeMap[ino] = &refCountedInode{node: n, refCount: 1} } func (n *pathInode) OnForget() { if n.clientInode == 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() { return } n.pathFs.pathLock.Lock() delete(n.pathFs.clientInodeMap, n.clientInode) n.pathFs.pathLock.Unlock() } //////////////////////////////////////////////////////////////// // FS operations func (n *pathInode) StatFs() *fuse.StatfsOut { return n.fs.StatFs(n.GetPath()) } func (n *pathInode) Readlink(c *fuse.Context) ([]byte, fuse.Status) { path := n.GetPath() val, err := n.fs.Readlink(path, c) return []byte(val), err } func (n *pathInode) Access(mode uint32, context *fuse.Context) (code fuse.Status) { p := n.GetPath() return n.fs.Access(p, mode, context) } func (n *pathInode) GetXAttr(attribute string, context *fuse.Context) (data []byte, code fuse.Status) { return n.fs.GetXAttr(n.GetPath(), attribute, context) } func (n *pathInode) RemoveXAttr(attr string, context *fuse.Context) fuse.Status { p := n.GetPath() return n.fs.RemoveXAttr(p, attr, context) } func (n *pathInode) SetXAttr(attr string, data []byte, flags int, context *fuse.Context) fuse.Status { return n.fs.SetXAttr(n.GetPath(), attr, data, flags, context) } func (n *pathInode) ListXAttr(context *fuse.Context) (attrs []string, code fuse.Status) { return n.fs.ListXAttr(n.GetPath(), context) } func (n *pathInode) Flush(file nodefs.File, openFlags uint32, context *fuse.Context) (code fuse.Status) { return file.Flush() } func (n *pathInode) OpenDir(context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { return n.fs.OpenDir(n.GetPath(), context) } func (n *pathInode) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) (*nodefs.Inode, fuse.Status) { fullPath := filepath.Join(n.GetPath(), name) code := n.fs.Mknod(fullPath, mode, dev, context) var child *nodefs.Inode if code.Ok() { pNode := n.createChild(name, false) child = pNode.Inode() } return child, code } func (n *pathInode) Mkdir(name string, mode uint32, context *fuse.Context) (*nodefs.Inode, fuse.Status) { fullPath := filepath.Join(n.GetPath(), name) code := n.fs.Mkdir(fullPath, mode, context) var child *nodefs.Inode if code.Ok() { pNode := n.createChild(name, true) child = pNode.Inode() } return child, code } func (n *pathInode) Unlink(name string, context *fuse.Context) (code fuse.Status) { code = n.fs.Unlink(filepath.Join(n.GetPath(), name), context) if code.Ok() { n.Inode().RmChild(name) } return code } func (n *pathInode) Rmdir(name string, context *fuse.Context) (code fuse.Status) { code = n.fs.Rmdir(filepath.Join(n.GetPath(), name), context) if code.Ok() { n.Inode().RmChild(name) } return code } func (n *pathInode) Symlink(name string, content string, context *fuse.Context) (*nodefs.Inode, fuse.Status) { fullPath := filepath.Join(n.GetPath(), name) code := n.fs.Symlink(content, fullPath, context) var child *nodefs.Inode if code.Ok() { pNode := n.createChild(name, false) child = pNode.Inode() } return child, code } func (n *pathInode) Rename(oldName string, newParent nodefs.Node, newName string, context *fuse.Context) (code fuse.Status) { p := newParent.(*pathInode) oldPath := filepath.Join(n.GetPath(), oldName) newPath := filepath.Join(p.GetPath(), newName) code = n.fs.Rename(oldPath, newPath, context) if code.Ok() { // The rename may have overwritten another file, remove it from the tree p.Inode().RmChild(newName) ch := n.Inode().RmChild(oldName) if ch != nil { // oldName may have been forgotten in the meantime. p.Inode().AddChild(newName, ch) } } return code } func (n *pathInode) Link(name string, existingFsnode nodefs.Node, context *fuse.Context) (*nodefs.Inode, fuse.Status) { if !n.pathFs.options.ClientInodes { return nil, fuse.ENOSYS } newPath := filepath.Join(n.GetPath(), name) existing := existingFsnode.(*pathInode) oldPath := existing.GetPath() code := n.fs.Link(oldPath, newPath, context) var a *fuse.Attr if code.Ok() { a, code = n.fs.GetAttr(newPath, context) } var child *nodefs.Inode if code.Ok() { if existing.clientInode != 0 && existing.clientInode == a.Ino { child = existing.Inode() n.Inode().AddChild(name, existing.Inode()) } else { pNode := n.createChild(name, false) child = pNode.Inode() pNode.clientInode = a.Ino } } return child, code } func (n *pathInode) Create(name string, flags uint32, mode uint32, context *fuse.Context) (nodefs.File, *nodefs.Inode, fuse.Status) { var child *nodefs.Inode fullPath := filepath.Join(n.GetPath(), name) file, code := n.fs.Create(fullPath, flags, mode, context) if code.Ok() { pNode := n.createChild(name, false) child = pNode.Inode() } return file, child, code } func (n *pathInode) createChild(name string, isDir bool) *pathInode { i := &pathInode{ fs: n.fs, pathFs: n.pathFs, } n.Inode().NewChild(name, isDir, i) return i } func (n *pathInode) Open(flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) { p := n.GetPath() file, code = n.fs.Open(p, flags, context) if n.pathFs.debug { file = &nodefs.WithFlags{ File: file, Description: n.GetPath(), } } return } func (n *pathInode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (node *nodefs.Inode, code fuse.Status) { fullPath := filepath.Join(n.GetPath(), name) fi, code := n.fs.GetAttr(fullPath, context) if code.Ok() { node = n.findChild(fi, name, fullPath).Inode() *out = *fi } return node, code } func (n *pathInode) findChild(fi *fuse.Attr, name string, fullPath string) (out *pathInode) { if fi.Ino > 0 { n.pathFs.pathLock.RLock() r := n.pathFs.clientInodeMap[fi.Ino] if r != nil { out = r.node r.refCount++ if fi.Nlink == 1 { log.Printf("Found linked inode, but Nlink == 1, ino=%d, fullPath=%q", fi.Ino, fullPath) } } n.pathFs.pathLock.RUnlock() } if out == nil { out = n.createChild(name, fi.IsDir()) out.setClientInode(fi.Ino) } else { n.Inode().AddChild(name, out.Inode()) } return out } func (n *pathInode) GetAttr(out *fuse.Attr, file nodefs.File, context *fuse.Context) (code fuse.Status) { var fi *fuse.Attr if file == nil { // Linux currently (tested on v4.4) does not pass a file descriptor for // fstat. To be able to stat a deleted file we have to find ourselves // an open fd. file = n.Inode().AnyFile() } // If we have found an open file, try to fstat it. if file != nil { code = file.GetAttr(out) if code.Ok() { return code } } // If we don't have an open file, or fstat on it failed due to an internal // error, stat by path. if file == nil || code == fuse.ENOSYS || code == fuse.EBADF { fi, code = n.fs.GetAttr(n.GetPath(), context) if !code.Ok() { return code } // This is a bug in the filesystem implementation, but let's not // crash. if fi == nil { log.Printf("Bug: fs.GetAttr returned OK with nil data") return fuse.EINVAL } } // Set inode number (unless already set or disabled). n.setClientInode(fi.Ino) // Help filesystems that forget to set Nlink. if !fi.IsDir() && fi.Nlink == 0 { fi.Nlink = 1 } *out = *fi return code } func (n *pathInode) Chmod(file nodefs.File, perms uint32, context *fuse.Context) (code fuse.Status) { // Note that Linux currently (Linux 4.4) DOES NOT pass a file descriptor // to FUSE for fchmod. We still check because that may change in the future. if file != nil { code = file.Chmod(perms) if code != fuse.ENOSYS { return code } } files := n.Inode().Files(fuse.O_ANYWRITE) for _, f := range files { // TODO - pass context code = f.Chmod(perms) if code.Ok() { return } } if len(files) == 0 || code == fuse.ENOSYS || code == fuse.EBADF { code = n.fs.Chmod(n.GetPath(), perms, context) } return code } func (n *pathInode) Chown(file nodefs.File, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) { // Note that Linux currently (Linux 4.4) DOES NOT pass a file descriptor // to FUSE for fchown. We still check because it may change in the future. if file != nil { code = file.Chown(uid, gid) if code != fuse.ENOSYS { return code } } files := n.Inode().Files(fuse.O_ANYWRITE) for _, f := range files { // TODO - pass context code = f.Chown(uid, gid) if code.Ok() { return code } } if len(files) == 0 || code == fuse.ENOSYS || code == fuse.EBADF { // TODO - can we get just FATTR_GID but not FATTR_UID ? code = n.fs.Chown(n.GetPath(), uid, gid, context) } return code } func (n *pathInode) Truncate(file nodefs.File, size uint64, context *fuse.Context) (code fuse.Status) { // A file descriptor was passed in AND the filesystem implements the // operation on the file handle. This the common case for ftruncate. if file != nil { code = file.Truncate(size) if code != fuse.ENOSYS { return code } } files := n.Inode().Files(fuse.O_ANYWRITE) for _, f := range files { // TODO - pass context code = f.Truncate(size) if code.Ok() { return code } } if len(files) == 0 || code == fuse.ENOSYS || code == fuse.EBADF { code = n.fs.Truncate(n.GetPath(), size, context) } return code } func (n *pathInode) Utimens(file nodefs.File, atime *time.Time, mtime *time.Time, context *fuse.Context) (code fuse.Status) { // Note that Linux currently (Linux 4.4) DOES NOT pass a file descriptor // to FUSE for futimens. We still check because it may change in the future. if file != nil { code = file.Utimens(atime, mtime) if code != fuse.ENOSYS { return code } } files := n.Inode().Files(fuse.O_ANYWRITE) for _, f := range files { // TODO - pass context code = f.Utimens(atime, mtime) if code.Ok() { return code } } if len(files) == 0 || code == fuse.ENOSYS || code == fuse.EBADF { code = n.fs.Utimens(n.GetPath(), atime, mtime, context) } return code } func (n *pathInode) Fallocate(file nodefs.File, off uint64, size uint64, mode uint32, context *fuse.Context) (code fuse.Status) { if file != nil { code = file.Allocate(off, size, mode) if code.Ok() { return code } } files := n.Inode().Files(fuse.O_ANYWRITE) for _, f := range files { // TODO - pass context code = f.Allocate(off, size, mode) if code.Ok() { return code } } return code } func (n *pathInode) Read(file nodefs.File, dest []byte, off int64, context *fuse.Context) (fuse.ReadResult, fuse.Status) { if file != nil { return file.Read(dest, off) } return nil, fuse.ENOSYS } func (n *pathInode) Write(file nodefs.File, data []byte, off int64, context *fuse.Context) (written uint32, code fuse.Status) { if file != nil { return file.Write(data, off) } return 0, fuse.ENOSYS }