kubelwagen/vendor/github.com/hanwen/go-fuse/fuse/pathfs/pathfs.go

747 lines
20 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 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
}