1295 lines
34 KiB
Go
1295 lines
34 KiB
Go
// Copyright 2019 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 fs
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"runtime/debug"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/hanwen/go-fuse/v2/fuse"
|
|
"github.com/hanwen/go-fuse/v2/internal"
|
|
)
|
|
|
|
func errnoToStatus(errno syscall.Errno) fuse.Status {
|
|
return fuse.Status(errno)
|
|
}
|
|
|
|
type fileEntry struct {
|
|
file FileHandle
|
|
|
|
// index into Inode.openFiles
|
|
nodeIndex int
|
|
|
|
// Handle number which we communicate to the kernel.
|
|
fh uint32
|
|
|
|
// Protects directory fields. Must be acquired before bridge.mu
|
|
mu sync.Mutex
|
|
|
|
// Directory
|
|
hasOverflow bool
|
|
overflow fuse.DirEntry
|
|
overflowErrno syscall.Errno
|
|
|
|
// Store the last read, in case readdir was interrupted.
|
|
lastRead []fuse.DirEntry
|
|
|
|
// dirOffset is the current location in the directory (see `telldir(3)`).
|
|
// The value is equivalent to `d_off` (see `getdents(2)`) of the last
|
|
// directory entry sent to the kernel so far.
|
|
// If `dirOffset` and `fuse.DirEntryList.offset` disagree, then a
|
|
// directory seek has taken place.
|
|
dirOffset uint64
|
|
|
|
// We try to associate a file for stat() calls, but the kernel
|
|
// can issue a RELEASE and GETATTR in parallel. This waitgroup
|
|
// avoids that the RELEASE will invalidate the file descriptor
|
|
// before we finish processing GETATTR.
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// ServerCallbacks are calls into the kernel to manipulate the inode,
|
|
// entry and page cache. They are stubbed so filesystems can be
|
|
// unittested without mounting them.
|
|
type ServerCallbacks interface {
|
|
DeleteNotify(parent uint64, child uint64, name string) fuse.Status
|
|
EntryNotify(parent uint64, name string) fuse.Status
|
|
InodeNotify(node uint64, off int64, length int64) fuse.Status
|
|
InodeRetrieveCache(node uint64, offset int64, dest []byte) (n int, st fuse.Status)
|
|
InodeNotifyStoreCache(node uint64, offset int64, data []byte) fuse.Status
|
|
}
|
|
|
|
// TODO: fold serverBackingFdCallbacks into ServerCallbacks and bump API version
|
|
type serverBackingFdCallbacks interface {
|
|
RegisterBackingFd(*fuse.BackingMap) (int32, syscall.Errno)
|
|
UnregisterBackingFd(id int32) syscall.Errno
|
|
}
|
|
|
|
type rawBridge struct {
|
|
options Options
|
|
root *Inode
|
|
server ServerCallbacks
|
|
|
|
// mu protects the following data. Locks for inodes must be
|
|
// taken before rawBridge.mu
|
|
mu sync.Mutex
|
|
|
|
// stableAttrs is used to detect already-known nodes and hard links by
|
|
// looking at:
|
|
// 1) file type ......... StableAttr.Mode
|
|
// 2) inode number ...... StableAttr.Ino
|
|
// 3) generation number . StableAttr.Gen
|
|
stableAttrs map[StableAttr]*Inode
|
|
automaticIno uint64
|
|
|
|
// The *Node ID* is an arbitrary uint64 identifier chosen by the FUSE library.
|
|
// It is used the identify *nodes* (files/directories/symlinks/...) in the
|
|
// communication between the FUSE library and the Linux kernel.
|
|
//
|
|
// The kernelNodeIds map translates between the NodeID and the corresponding
|
|
// go-fuse Inode object.
|
|
//
|
|
// A simple incrementing counter is used as the NodeID (see `nextNodeID`).
|
|
kernelNodeIds map[uint64]*Inode
|
|
// nextNodeID is the next free NodeID. Increment after copying the value.
|
|
nextNodeId uint64
|
|
// nodeCountHigh records the highest number of entries we had in the
|
|
// kernelNodeIds map.
|
|
// As the size of stableAttrs tracks kernelNodeIds (+- a few entries due to
|
|
// concurrent FORGETs, LOOKUPs, and the fixed NodeID 1), this is also a good
|
|
// estimate for stableAttrs.
|
|
nodeCountHigh int
|
|
|
|
files []*fileEntry
|
|
|
|
// indices of files that are not allocated.
|
|
freeFiles []uint32
|
|
|
|
// If set, don't try to register backing file for Create/Open calls.
|
|
disableBackingFiles bool
|
|
}
|
|
|
|
// newInode creates creates new inode pointing to ops.
|
|
func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persistent bool) *Inode {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if id.Reserved() {
|
|
log.Panicf("using reserved ID %d for inode number", id.Ino)
|
|
}
|
|
|
|
// This ops already was populated. Just return it.
|
|
if ops.embed().bridge != nil {
|
|
return ops.embed()
|
|
}
|
|
|
|
// Only the file type bits matter
|
|
id.Mode = id.Mode & syscall.S_IFMT
|
|
if id.Mode == 0 {
|
|
id.Mode = fuse.S_IFREG
|
|
}
|
|
|
|
if id.Ino == 0 {
|
|
// Find free inode number.
|
|
for {
|
|
id.Ino = b.automaticIno
|
|
b.automaticIno++
|
|
_, ok := b.stableAttrs[id]
|
|
if !ok {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
initInode(ops.embed(), ops, id, b, persistent, b.nextNodeId)
|
|
b.nextNodeId++
|
|
return ops.embed()
|
|
}
|
|
|
|
func (b *rawBridge) logf(format string, args ...interface{}) {
|
|
if b.options.Logger != nil {
|
|
b.options.Logger.Printf(format, args...)
|
|
}
|
|
}
|
|
|
|
func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode {
|
|
ch := b.newInodeUnlocked(ops, id, persistent)
|
|
if ch != ops.embed() {
|
|
return ch
|
|
}
|
|
|
|
if oa, ok := ops.(NodeOnAdder); ok {
|
|
oa.OnAdd(ctx)
|
|
}
|
|
return ch
|
|
}
|
|
|
|
// addNewChild inserts the child into the tree. Returns file handle if file != nil.
|
|
// Unless fileFlags has the syscall.O_EXCL bit set, child.stableAttr will be used
|
|
// to find an already-known node. If one is found, `child` is ignored and the
|
|
// already-known one is used. The node that was actually used is returned.
|
|
func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file FileHandle, fileFlags uint32, out *fuse.EntryOut) (selected *Inode, fe *fileEntry) {
|
|
if name == "." || name == ".." {
|
|
log.Panicf("BUG: tried to add virtual entry %q to the actual tree", name)
|
|
}
|
|
|
|
// the same node can be looked up through 2 paths in parallel, eg.
|
|
//
|
|
// root
|
|
// / \
|
|
// dir1 dir2
|
|
// \ /
|
|
// file
|
|
//
|
|
// dir1.Lookup("file") and dir2.Lookup("file") are executed
|
|
// simultaneously. The matching StableAttrs ensure that we return the
|
|
// same node.
|
|
orig := child
|
|
id := child.stableAttr
|
|
if id.Mode & ^(uint32(syscall.S_IFMT)) != 0 {
|
|
log.Panicf("%#v", id)
|
|
}
|
|
for {
|
|
lockNodes(parent, child)
|
|
b.mu.Lock()
|
|
if fileFlags&syscall.O_EXCL != 0 {
|
|
// must create a new node - don't look for existing nodes
|
|
break
|
|
}
|
|
old := b.stableAttrs[id]
|
|
if old == nil {
|
|
if child == orig {
|
|
// no pre-existing node under this inode number
|
|
break
|
|
} else {
|
|
// old inode disappeared while we were looping here. Go back to
|
|
// original child.
|
|
b.mu.Unlock()
|
|
unlockNodes(parent, child)
|
|
child = orig
|
|
continue
|
|
}
|
|
}
|
|
if old == child {
|
|
// we now have the right inode locked
|
|
break
|
|
}
|
|
// found a different existing node
|
|
b.mu.Unlock()
|
|
unlockNodes(parent, child)
|
|
child = old
|
|
}
|
|
|
|
child.lookupCount++
|
|
child.changeCounter++
|
|
|
|
b.kernelNodeIds[child.nodeId] = child
|
|
if len(b.kernelNodeIds) > b.nodeCountHigh {
|
|
b.nodeCountHigh = len(b.kernelNodeIds)
|
|
}
|
|
// Any node that might be there is overwritten - it is obsolete now
|
|
b.stableAttrs[id] = child
|
|
if file != nil {
|
|
fe = b.registerFile(child, file, fileFlags)
|
|
}
|
|
|
|
parent.setEntry(name, child)
|
|
|
|
out.NodeId = child.nodeId
|
|
out.Generation = child.stableAttr.Gen
|
|
out.Attr.Ino = child.stableAttr.Ino
|
|
|
|
b.mu.Unlock()
|
|
unlockNodes(parent, child)
|
|
|
|
return child, fe
|
|
}
|
|
|
|
func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) {
|
|
b.setAttr(&out.Attr)
|
|
if b.options.AttrTimeout != nil && out.AttrTimeout() == 0 {
|
|
out.SetAttrTimeout(*b.options.AttrTimeout)
|
|
}
|
|
if b.options.EntryTimeout != nil && out.EntryTimeout() == 0 {
|
|
out.SetEntryTimeout(*b.options.EntryTimeout)
|
|
}
|
|
}
|
|
|
|
func (b *rawBridge) setAttr(out *fuse.Attr) {
|
|
if !b.options.NullPermissions && out.Mode&07777 == 0 {
|
|
out.Mode |= 0644
|
|
if out.Mode&syscall.S_IFDIR != 0 {
|
|
out.Mode |= 0111
|
|
}
|
|
}
|
|
if b.options.UID != 0 && out.Uid == 0 {
|
|
out.Uid = b.options.UID
|
|
}
|
|
if b.options.GID != 0 && out.Gid == 0 {
|
|
out.Gid = b.options.GID
|
|
}
|
|
setBlocks(out)
|
|
}
|
|
|
|
func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) {
|
|
if b.options.AttrTimeout != nil && out.Timeout() == 0 {
|
|
out.SetTimeout(*b.options.AttrTimeout)
|
|
}
|
|
}
|
|
|
|
// NewNodeFS creates a node based filesystem based on the
|
|
// InodeEmbedder instance for the root of the tree.
|
|
func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem {
|
|
bridge := &rawBridge{
|
|
automaticIno: opts.FirstAutomaticIno,
|
|
server: opts.ServerCallbacks,
|
|
nextNodeId: 2, // the root node has nodeid 1
|
|
stableAttrs: make(map[StableAttr]*Inode),
|
|
}
|
|
|
|
if bridge.automaticIno == 0 {
|
|
bridge.automaticIno = 1 << 63
|
|
}
|
|
|
|
if opts != nil {
|
|
bridge.options = *opts
|
|
} else {
|
|
oneSec := time.Second
|
|
bridge.options.EntryTimeout = &oneSec
|
|
bridge.options.AttrTimeout = &oneSec
|
|
}
|
|
|
|
stableAttr := StableAttr{
|
|
Ino: root.embed().StableAttr().Ino,
|
|
Mode: fuse.S_IFDIR,
|
|
}
|
|
if opts.RootStableAttr != nil {
|
|
stableAttr.Ino = opts.RootStableAttr.Ino
|
|
stableAttr.Gen = opts.RootStableAttr.Gen
|
|
}
|
|
|
|
initInode(root.embed(), root,
|
|
stableAttr,
|
|
bridge,
|
|
false,
|
|
1,
|
|
)
|
|
bridge.root = root.embed()
|
|
bridge.root.lookupCount = 1
|
|
bridge.kernelNodeIds = map[uint64]*Inode{
|
|
1: bridge.root,
|
|
}
|
|
|
|
// Fh 0 means no file handle.
|
|
bridge.files = []*fileEntry{{}}
|
|
|
|
if opts.OnAdd != nil {
|
|
opts.OnAdd(context.Background())
|
|
} else if oa, ok := root.(NodeOnAdder); ok {
|
|
oa.OnAdd(context.Background())
|
|
}
|
|
|
|
return bridge
|
|
}
|
|
|
|
func (b *rawBridge) String() string {
|
|
return "rawBridge"
|
|
}
|
|
|
|
func (b *rawBridge) inode(id uint64, fh uint64) (*Inode, *fileEntry) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
n, f := b.kernelNodeIds[id], b.files[fh]
|
|
if n == nil {
|
|
log.Panicf("unknown node %d", id)
|
|
}
|
|
return n, f
|
|
}
|
|
|
|
func (b *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name string, out *fuse.EntryOut) fuse.Status {
|
|
parent, _ := b.inode(header.NodeId, 0)
|
|
ctx := &fuse.Context{Caller: header.Caller, Cancel: cancel}
|
|
child, errno := b.lookup(ctx, parent, name, out)
|
|
|
|
if errno != 0 {
|
|
if errno == syscall.ENOENT && b.options.NegativeTimeout != nil && out.EntryTimeout() == 0 {
|
|
out.SetEntryTimeout(*b.options.NegativeTimeout)
|
|
errno = 0
|
|
}
|
|
return errnoToStatus(errno)
|
|
}
|
|
|
|
child, _ = b.addNewChild(parent, name, child, nil, 0, out)
|
|
child.setEntryOut(out)
|
|
b.setEntryOutTimeout(out)
|
|
return fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) lookup(ctx *fuse.Context, parent *Inode, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
|
if lu, ok := parent.ops.(NodeLookuper); ok {
|
|
return lu.Lookup(ctx, name, out)
|
|
}
|
|
|
|
child := parent.GetChild(name)
|
|
if child == nil {
|
|
return nil, syscall.ENOENT
|
|
}
|
|
|
|
if ga, ok := child.ops.(NodeGetattrer); ok {
|
|
var a fuse.AttrOut
|
|
errno := ga.Getattr(ctx, nil, &a)
|
|
if errno == 0 {
|
|
out.Attr = a.Attr
|
|
}
|
|
}
|
|
|
|
return child, OK
|
|
}
|
|
|
|
func (b *rawBridge) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name string) fuse.Status {
|
|
parent, _ := b.inode(header.NodeId, 0)
|
|
var errno syscall.Errno
|
|
if mops, ok := parent.ops.(NodeRmdirer); ok {
|
|
errno = mops.Rmdir(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name)
|
|
}
|
|
|
|
// TODO - this should not succeed silently.
|
|
|
|
if errno == 0 {
|
|
parent.RmChild(name)
|
|
}
|
|
return errnoToStatus(errno)
|
|
}
|
|
|
|
func (b *rawBridge) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name string) fuse.Status {
|
|
parent, _ := b.inode(header.NodeId, 0)
|
|
var errno syscall.Errno
|
|
if mops, ok := parent.ops.(NodeUnlinker); ok {
|
|
errno = mops.Unlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name)
|
|
}
|
|
|
|
// TODO - this should not succeed silently.
|
|
|
|
if errno == 0 {
|
|
parent.RmChild(name)
|
|
}
|
|
return errnoToStatus(errno)
|
|
}
|
|
|
|
func (b *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name string, out *fuse.EntryOut) fuse.Status {
|
|
parent, _ := b.inode(input.NodeId, 0)
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
mops, ok := parent.ops.(NodeMkdirer)
|
|
if !ok {
|
|
return fuse.ENOTSUP
|
|
}
|
|
child, errno := mops.Mkdir(ctx, name, input.Mode, out)
|
|
|
|
if errno != 0 {
|
|
return errnoToStatus(errno)
|
|
}
|
|
|
|
if out.Attr.Mode&^07777 == 0 {
|
|
out.Attr.Mode |= fuse.S_IFDIR
|
|
}
|
|
|
|
if out.Attr.Mode&^07777 != fuse.S_IFDIR {
|
|
log.Panicf("Mkdir: mode must be S_IFDIR (%o), got %o", fuse.S_IFDIR, out.Attr.Mode)
|
|
}
|
|
|
|
child, _ = b.addNewChild(parent, name, child, nil, syscall.O_EXCL, out)
|
|
child.setEntryOut(out)
|
|
b.setEntryOutTimeout(out)
|
|
return fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) Mknod(cancel <-chan struct{}, input *fuse.MknodIn, name string, out *fuse.EntryOut) fuse.Status {
|
|
parent, _ := b.inode(input.NodeId, 0)
|
|
|
|
mops, ok := parent.ops.(NodeMknoder)
|
|
if !ok {
|
|
return fuse.ENOTSUP
|
|
}
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
child, errno := mops.Mknod(ctx, name, input.Mode, input.Rdev, out)
|
|
if errno != 0 {
|
|
return errnoToStatus(errno)
|
|
}
|
|
|
|
child, _ = b.addNewChild(parent, name, child, nil, syscall.O_EXCL, out)
|
|
child.setEntryOut(out)
|
|
b.setEntryOutTimeout(out)
|
|
return fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name string, out *fuse.CreateOut) fuse.Status {
|
|
parent, _ := b.inode(input.NodeId, 0)
|
|
|
|
mops, ok := parent.ops.(NodeCreater)
|
|
if !ok {
|
|
return fuse.EROFS
|
|
}
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
child, f, flags, errno := mops.Create(ctx, name, input.Flags, input.Mode, &out.EntryOut)
|
|
|
|
if errno != 0 {
|
|
return errnoToStatus(errno)
|
|
}
|
|
|
|
child, fe := b.addNewChild(parent, name, child, f, input.Flags|syscall.O_CREAT|syscall.O_EXCL, &out.EntryOut)
|
|
out.Fh = uint64(fe.fh)
|
|
out.OpenFlags = flags
|
|
|
|
b.addBackingID(child, f, &out.OpenOut)
|
|
child.setEntryOut(&out.EntryOut)
|
|
b.setEntryOutTimeout(&out.EntryOut)
|
|
return fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) Forget(nodeid, nlookup uint64) {
|
|
n, _ := b.inode(nodeid, 0)
|
|
hasLookups, _, _ := n.removeRef(nlookup, false)
|
|
|
|
if !hasLookups {
|
|
b.compactMemory()
|
|
}
|
|
}
|
|
|
|
// compactMemory tries to free memory that was previously used by forgotten
|
|
// nodes.
|
|
//
|
|
// Maps do not free all memory when elements get deleted
|
|
// ( https://github.com/golang/go/issues/20135 ).
|
|
// As a workaround, we recreate our two big maps (stableAttrs & kernelNodeIds)
|
|
// every time they have shrunk dramatically (100 x smaller).
|
|
// In this case, `nodeCountHigh` is reset to the new (smaller) size.
|
|
func (b *rawBridge) compactMemory() {
|
|
b.mu.Lock()
|
|
|
|
if b.nodeCountHigh <= len(b.kernelNodeIds)*100 {
|
|
b.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
tmpStableAttrs := make(map[StableAttr]*Inode, len(b.stableAttrs))
|
|
for i, v := range b.stableAttrs {
|
|
tmpStableAttrs[i] = v
|
|
}
|
|
b.stableAttrs = tmpStableAttrs
|
|
|
|
tmpKernelNodeIds := make(map[uint64]*Inode, len(b.kernelNodeIds))
|
|
for i, v := range b.kernelNodeIds {
|
|
tmpKernelNodeIds[i] = v
|
|
}
|
|
b.kernelNodeIds = tmpKernelNodeIds
|
|
|
|
b.nodeCountHigh = len(b.kernelNodeIds)
|
|
|
|
b.mu.Unlock()
|
|
|
|
// Run outside b.mu
|
|
debug.FreeOSMemory()
|
|
}
|
|
|
|
func (b *rawBridge) SetDebug(debug bool) {}
|
|
|
|
func (b *rawBridge) GetAttr(cancel <-chan struct{}, input *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Status {
|
|
n, fEntry := b.inode(input.NodeId, input.Fh())
|
|
f := fEntry.file
|
|
if f == nil {
|
|
// The linux kernel doesnt pass along the file
|
|
// descriptor, so we have to fake it here.
|
|
// See https://github.com/libfuse/libfuse/issues/62
|
|
b.mu.Lock()
|
|
for _, fh := range n.openFiles {
|
|
f = b.files[fh].file
|
|
b.files[fh].wg.Add(1)
|
|
defer b.files[fh].wg.Done()
|
|
break
|
|
}
|
|
b.mu.Unlock()
|
|
}
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
return errnoToStatus(b.getattr(ctx, n, f, out))
|
|
}
|
|
|
|
func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fuse.AttrOut) syscall.Errno {
|
|
var errno syscall.Errno
|
|
|
|
if nodeOps, ok := n.ops.(NodeGetattrer); ok {
|
|
errno = nodeOps.Getattr(ctx, f, out)
|
|
} else if fileOps, ok := f.(FileGetattrer); ok {
|
|
errno = fileOps.Getattr(ctx, out)
|
|
} else {
|
|
// We set Mode below, which is the minimum for success
|
|
}
|
|
|
|
if errno == 0 {
|
|
if out.Ino != 0 && n.stableAttr.Ino > 1 && out.Ino != n.stableAttr.Ino {
|
|
b.logf("warning: rawBridge.getattr: overriding ino %d with %d", out.Ino, n.stableAttr.Ino)
|
|
}
|
|
out.Ino = n.stableAttr.Ino
|
|
out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode
|
|
b.setAttr(&out.Attr)
|
|
b.setAttrTimeout(out)
|
|
}
|
|
return errno
|
|
}
|
|
|
|
func (b *rawBridge) SetAttr(cancel <-chan struct{}, in *fuse.SetAttrIn, out *fuse.AttrOut) fuse.Status {
|
|
ctx := &fuse.Context{Caller: in.Caller, Cancel: cancel}
|
|
|
|
fh, _ := in.GetFh()
|
|
|
|
n, fEntry := b.inode(in.NodeId, fh)
|
|
f := fEntry.file
|
|
|
|
var errno = syscall.ENOTSUP
|
|
if fops, ok := n.ops.(NodeSetattrer); ok {
|
|
errno = fops.Setattr(ctx, f, in, out)
|
|
} else if fops, ok := f.(FileSetattrer); ok {
|
|
errno = fops.Setattr(ctx, in, out)
|
|
}
|
|
|
|
out.Mode = n.stableAttr.Mode | (out.Mode & 07777)
|
|
return errnoToStatus(errno)
|
|
}
|
|
|
|
func (b *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName string, newName string) fuse.Status {
|
|
p1, _ := b.inode(input.NodeId, 0)
|
|
p2, _ := b.inode(input.Newdir, 0)
|
|
|
|
if mops, ok := p1.ops.(NodeRenamer); ok {
|
|
errno := mops.Rename(&fuse.Context{Caller: input.Caller, Cancel: cancel}, oldName, p2.ops, newName, input.Flags)
|
|
if errno == 0 {
|
|
if input.Flags&RENAME_EXCHANGE != 0 {
|
|
p1.ExchangeChild(oldName, p2, newName)
|
|
} else {
|
|
// MvChild cannot fail with overwrite=true.
|
|
_ = p1.MvChild(oldName, p2, newName, true)
|
|
}
|
|
}
|
|
return errnoToStatus(errno)
|
|
}
|
|
return fuse.ENOTSUP
|
|
}
|
|
|
|
func (b *rawBridge) Link(cancel <-chan struct{}, input *fuse.LinkIn, name string, out *fuse.EntryOut) fuse.Status {
|
|
parent, _ := b.inode(input.NodeId, 0)
|
|
target, _ := b.inode(input.Oldnodeid, 0)
|
|
|
|
mops, ok := parent.ops.(NodeLinker)
|
|
if !ok {
|
|
return fuse.ENOTSUP
|
|
}
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
child, errno := mops.Link(ctx, target.ops, name, out)
|
|
if errno != 0 {
|
|
return errnoToStatus(errno)
|
|
}
|
|
|
|
child, _ = b.addNewChild(parent, name, child, nil, 0, out)
|
|
child.setEntryOut(out)
|
|
b.setEntryOutTimeout(out)
|
|
return fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target string, name string, out *fuse.EntryOut) fuse.Status {
|
|
parent, _ := b.inode(header.NodeId, 0)
|
|
|
|
mops, ok := parent.ops.(NodeSymlinker)
|
|
if !ok {
|
|
return fuse.ENOTSUP
|
|
}
|
|
ctx := &fuse.Context{Caller: header.Caller, Cancel: cancel}
|
|
child, status := mops.Symlink(ctx, target, name, out)
|
|
if status != 0 {
|
|
return errnoToStatus(status)
|
|
}
|
|
|
|
child, _ = b.addNewChild(parent, name, child, nil, syscall.O_EXCL, out)
|
|
child.setEntryOut(out)
|
|
b.setEntryOutTimeout(out)
|
|
return fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) Readlink(cancel <-chan struct{}, header *fuse.InHeader) (out []byte, status fuse.Status) {
|
|
n, _ := b.inode(header.NodeId, 0)
|
|
|
|
linker, ok := n.ops.(NodeReadlinker)
|
|
if !ok {
|
|
return nil, fuse.ENOTSUP
|
|
}
|
|
ctx := &fuse.Context{Caller: header.Caller, Cancel: cancel}
|
|
result, errno := linker.Readlink(ctx)
|
|
if errno != 0 {
|
|
return nil, errnoToStatus(errno)
|
|
}
|
|
|
|
return result, fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) Access(cancel <-chan struct{}, input *fuse.AccessIn) fuse.Status {
|
|
n, _ := b.inode(input.NodeId, 0)
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if a, ok := n.ops.(NodeAccesser); ok {
|
|
return errnoToStatus(a.Access(ctx, input.Mask))
|
|
}
|
|
|
|
// default: check attributes.
|
|
caller := input.Caller
|
|
|
|
var out fuse.AttrOut
|
|
if s := b.getattr(ctx, n, nil, &out); s != 0 {
|
|
return errnoToStatus(s)
|
|
}
|
|
|
|
if !internal.HasAccess(caller.Uid, caller.Gid, out.Uid, out.Gid, out.Mode, input.Mask) {
|
|
return fuse.EACCES
|
|
}
|
|
return fuse.OK
|
|
}
|
|
|
|
// Extended attributes.
|
|
|
|
func (b *rawBridge) GetXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string, data []byte) (uint32, fuse.Status) {
|
|
n, _ := b.inode(header.NodeId, 0)
|
|
|
|
if xops, ok := n.ops.(NodeGetxattrer); ok {
|
|
nb, errno := xops.Getxattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, attr, data)
|
|
return nb, errnoToStatus(errno)
|
|
}
|
|
|
|
return 0, fuse.ENOATTR
|
|
}
|
|
|
|
func (b *rawBridge) ListXAttr(cancel <-chan struct{}, header *fuse.InHeader, dest []byte) (sz uint32, status fuse.Status) {
|
|
n, _ := b.inode(header.NodeId, 0)
|
|
if xops, ok := n.ops.(NodeListxattrer); ok {
|
|
sz, errno := xops.Listxattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, dest)
|
|
return sz, errnoToStatus(errno)
|
|
}
|
|
return 0, fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) SetXAttr(cancel <-chan struct{}, input *fuse.SetXAttrIn, attr string, data []byte) fuse.Status {
|
|
n, _ := b.inode(input.NodeId, 0)
|
|
if xops, ok := n.ops.(NodeSetxattrer); ok {
|
|
return errnoToStatus(xops.Setxattr(&fuse.Context{Caller: input.Caller, Cancel: cancel}, attr, data, input.Flags))
|
|
}
|
|
return fuse.ENOATTR
|
|
}
|
|
|
|
func (b *rawBridge) RemoveXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string) fuse.Status {
|
|
n, _ := b.inode(header.NodeId, 0)
|
|
if xops, ok := n.ops.(NodeRemovexattrer); ok {
|
|
return errnoToStatus(xops.Removexattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, attr))
|
|
}
|
|
return fuse.ENOATTR
|
|
}
|
|
|
|
func (b *rawBridge) Open(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status {
|
|
n, _ := b.inode(input.NodeId, 0)
|
|
|
|
op, ok := n.ops.(NodeOpener)
|
|
if !ok {
|
|
return fuse.ENOTSUP
|
|
}
|
|
f, flags, errno := op.Open(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Flags)
|
|
if errno != 0 {
|
|
return errnoToStatus(errno)
|
|
}
|
|
out.OpenFlags = flags
|
|
|
|
if f != nil {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
fe := b.registerFile(n, f, input.Flags)
|
|
out.Fh = uint64(fe.fh)
|
|
|
|
b.addBackingID(n, f, out)
|
|
}
|
|
return fuse.OK
|
|
}
|
|
|
|
// must hold bridge.mu
|
|
func (b *rawBridge) addBackingID(n *Inode, f FileHandle, out *fuse.OpenOut) {
|
|
if b.disableBackingFiles {
|
|
return
|
|
}
|
|
|
|
bc, ok := b.server.(serverBackingFdCallbacks)
|
|
if !ok {
|
|
b.disableBackingFiles = true
|
|
return
|
|
}
|
|
pth, ok := f.(FilePassthroughFder)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if n.backingID == 0 {
|
|
fd, ok := pth.PassthroughFd()
|
|
if !ok {
|
|
return
|
|
}
|
|
m := fuse.BackingMap{
|
|
Fd: int32(fd),
|
|
}
|
|
id, errno := bc.RegisterBackingFd(&m)
|
|
if errno != 0 {
|
|
// This happens if we're not root or CAP_PASSTHROUGH is missing.
|
|
b.disableBackingFiles = true
|
|
} else {
|
|
n.backingID = id
|
|
}
|
|
}
|
|
|
|
if n.backingID != 0 {
|
|
out.BackingID = n.backingID
|
|
out.OpenFlags |= fuse.FOPEN_PASSTHROUGH
|
|
out.OpenFlags &= ^uint32(fuse.FOPEN_KEEP_CACHE)
|
|
n.backingIDRefcount++
|
|
}
|
|
}
|
|
|
|
// must hold bridge.mu
|
|
func (b *rawBridge) releaseBackingIDRef(n *Inode) {
|
|
if n.backingID == 0 {
|
|
return
|
|
}
|
|
|
|
n.backingIDRefcount--
|
|
if n.backingIDRefcount == 0 {
|
|
errno := b.server.(serverBackingFdCallbacks).UnregisterBackingFd(n.backingID)
|
|
if errno != 0 {
|
|
b.logf("UnregisterBackingFd: %v", errno)
|
|
}
|
|
n.backingID = 0
|
|
n.backingIDRefcount = 0
|
|
} else if n.backingIDRefcount < 0 {
|
|
log.Panic("backingIDRefcount underflow")
|
|
}
|
|
}
|
|
|
|
// registerFile hands out a file handle. Must have bridge.mu. Flags are the open flags
|
|
// (eg. syscall.O_EXCL).
|
|
func (b *rawBridge) registerFile(n *Inode, f FileHandle, flags uint32) *fileEntry {
|
|
fe := &fileEntry{}
|
|
if len(b.freeFiles) > 0 {
|
|
last := len(b.freeFiles) - 1
|
|
fe.fh = b.freeFiles[last]
|
|
b.freeFiles = b.freeFiles[:last]
|
|
b.files[fe.fh] = fe
|
|
} else {
|
|
fe.fh = uint32(len(b.files))
|
|
b.files = append(b.files, fe)
|
|
}
|
|
|
|
if _, ok := f.(FileReaddirenter); ok {
|
|
fe.lastRead = make([]fuse.DirEntry, 0, 100)
|
|
}
|
|
fe.nodeIndex = len(n.openFiles)
|
|
fe.file = f
|
|
n.openFiles = append(n.openFiles, fe.fh)
|
|
|
|
return fe
|
|
}
|
|
|
|
func (b *rawBridge) Read(cancel <-chan struct{}, input *fuse.ReadIn, buf []byte) (fuse.ReadResult, fuse.Status) {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if fops, ok := n.ops.(NodeReader); ok {
|
|
res, errno := fops.Read(ctx, f.file, buf, int64(input.Offset))
|
|
return res, errnoToStatus(errno)
|
|
}
|
|
if fr, ok := f.file.(FileReader); ok {
|
|
res, errno := fr.Read(ctx, buf, int64(input.Offset))
|
|
return res, errnoToStatus(errno)
|
|
}
|
|
|
|
return nil, fuse.ENOTSUP
|
|
}
|
|
|
|
func (b *rawBridge) GetLk(cancel <-chan struct{}, input *fuse.LkIn, out *fuse.LkOut) fuse.Status {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if lops, ok := n.ops.(NodeGetlker); ok {
|
|
return errnoToStatus(lops.Getlk(ctx, f.file, input.Owner, &input.Lk, input.LkFlags, &out.Lk))
|
|
}
|
|
if gl, ok := f.file.(FileGetlker); ok {
|
|
return errnoToStatus(gl.Getlk(ctx, input.Owner, &input.Lk, input.LkFlags, &out.Lk))
|
|
}
|
|
return fuse.ENOTSUP
|
|
}
|
|
|
|
func (b *rawBridge) SetLk(cancel <-chan struct{}, input *fuse.LkIn) fuse.Status {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if lops, ok := n.ops.(NodeSetlker); ok {
|
|
return errnoToStatus(lops.Setlk(ctx, f.file, input.Owner, &input.Lk, input.LkFlags))
|
|
}
|
|
if sl, ok := f.file.(FileSetlker); ok {
|
|
return errnoToStatus(sl.Setlk(ctx, input.Owner, &input.Lk, input.LkFlags))
|
|
}
|
|
return fuse.ENOTSUP
|
|
}
|
|
func (b *rawBridge) SetLkw(cancel <-chan struct{}, input *fuse.LkIn) fuse.Status {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if lops, ok := n.ops.(NodeSetlkwer); ok {
|
|
return errnoToStatus(lops.Setlkw(ctx, f.file, input.Owner, &input.Lk, input.LkFlags))
|
|
}
|
|
if sl, ok := f.file.(FileSetlkwer); ok {
|
|
return errnoToStatus(sl.Setlkw(ctx, input.Owner, &input.Lk, input.LkFlags))
|
|
}
|
|
return fuse.ENOTSUP
|
|
}
|
|
|
|
func (b *rawBridge) Release(cancel <-chan struct{}, input *fuse.ReleaseIn) {
|
|
n, f := b.releaseFileEntry(input.NodeId, input.Fh)
|
|
if f == nil {
|
|
return
|
|
}
|
|
|
|
f.wg.Wait()
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if r, ok := n.ops.(NodeReleaser); ok {
|
|
r.Release(ctx, f.file)
|
|
} else if r, ok := f.file.(FileReleaser); ok {
|
|
r.Release(ctx)
|
|
}
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
b.releaseBackingIDRef(n)
|
|
b.freeFiles = append(b.freeFiles, uint32(input.Fh))
|
|
}
|
|
|
|
func (b *rawBridge) ReleaseDir(input *fuse.ReleaseIn) {
|
|
n, f := b.releaseFileEntry(input.NodeId, input.Fh)
|
|
f.wg.Wait()
|
|
|
|
if frd, ok := f.file.(FileReleasedirer); ok {
|
|
frd.Releasedir(context.Background(), input.ReleaseFlags)
|
|
}
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.releaseBackingIDRef(n)
|
|
b.freeFiles = append(b.freeFiles, uint32(input.Fh))
|
|
}
|
|
|
|
func (b *rawBridge) releaseFileEntry(nid uint64, fh uint64) (*Inode, *fileEntry) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
n := b.kernelNodeIds[nid]
|
|
var entry *fileEntry
|
|
if fh > 0 {
|
|
last := len(n.openFiles) - 1
|
|
entry = b.files[fh]
|
|
if last != entry.nodeIndex {
|
|
n.openFiles[entry.nodeIndex] = n.openFiles[last]
|
|
|
|
b.files[n.openFiles[entry.nodeIndex]].nodeIndex = entry.nodeIndex
|
|
}
|
|
n.openFiles = n.openFiles[:last]
|
|
}
|
|
return n, entry
|
|
}
|
|
|
|
func (b *rawBridge) Write(cancel <-chan struct{}, input *fuse.WriteIn, data []byte) (written uint32, status fuse.Status) {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if wr, ok := n.ops.(NodeWriter); ok {
|
|
w, errno := wr.Write(ctx, f.file, data, int64(input.Offset))
|
|
return w, errnoToStatus(errno)
|
|
}
|
|
if fr, ok := f.file.(FileWriter); ok {
|
|
w, errno := fr.Write(ctx, data, int64(input.Offset))
|
|
return w, errnoToStatus(errno)
|
|
}
|
|
|
|
return 0, fuse.ENOTSUP
|
|
}
|
|
|
|
func (b *rawBridge) Flush(cancel <-chan struct{}, input *fuse.FlushIn) fuse.Status {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if fl, ok := n.ops.(NodeFlusher); ok {
|
|
return errnoToStatus(fl.Flush(ctx, f.file))
|
|
}
|
|
if fl, ok := f.file.(FileFlusher); ok {
|
|
return errnoToStatus(fl.Flush(ctx))
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (b *rawBridge) Fsync(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if fs, ok := n.ops.(NodeFsyncer); ok {
|
|
return errnoToStatus(fs.Fsync(ctx, f.file, input.FsyncFlags))
|
|
}
|
|
if fs, ok := f.file.(FileFsyncer); ok {
|
|
return errnoToStatus(fs.Fsync(ctx, input.FsyncFlags))
|
|
}
|
|
return fuse.ENOTSUP
|
|
}
|
|
|
|
func (b *rawBridge) Fallocate(cancel <-chan struct{}, input *fuse.FallocateIn) fuse.Status {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if a, ok := n.ops.(NodeAllocater); ok {
|
|
return errnoToStatus(a.Allocate(ctx, f.file, input.Offset, input.Length, input.Mode))
|
|
}
|
|
if a, ok := f.file.(FileAllocater); ok {
|
|
return errnoToStatus(a.Allocate(ctx, input.Offset, input.Length, input.Mode))
|
|
}
|
|
return fuse.ENOTSUP
|
|
}
|
|
|
|
func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status {
|
|
n, _ := b.inode(input.NodeId, 0)
|
|
|
|
var fh FileHandle
|
|
var fuseFlags uint32
|
|
var errno syscall.Errno
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
|
|
nod, _ := n.ops.(NodeOpendirer)
|
|
nrd, _ := n.ops.(NodeReaddirer)
|
|
|
|
if odh, ok := n.ops.(NodeOpendirHandler); ok {
|
|
fh, fuseFlags, errno = odh.OpendirHandle(ctx, input.Flags)
|
|
|
|
if errno != 0 {
|
|
return errnoToStatus(errno)
|
|
}
|
|
} else {
|
|
if nod != nil {
|
|
errno = nod.Opendir(ctx)
|
|
if errno != 0 {
|
|
return errnoToStatus(errno)
|
|
}
|
|
}
|
|
|
|
var ctor func(context.Context) (DirStream, syscall.Errno)
|
|
if nrd != nil {
|
|
ctor = func(ctx context.Context) (DirStream, syscall.Errno) {
|
|
return nrd.Readdir(ctx)
|
|
}
|
|
} else {
|
|
ctor = func(ctx context.Context) (DirStream, syscall.Errno) {
|
|
return n.childrenAsDirstream(), 0
|
|
}
|
|
}
|
|
fh = &dirStreamAsFile{creator: ctor}
|
|
}
|
|
|
|
if fuseFlags&(fuse.FOPEN_CACHE_DIR|fuse.FOPEN_KEEP_CACHE) != 0 {
|
|
fuseFlags |= fuse.FOPEN_CACHE_DIR | fuse.FOPEN_KEEP_CACHE
|
|
}
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
fe := b.registerFile(n, fh, 0)
|
|
out.Fh = uint64(fe.fh)
|
|
out.OpenFlags = fuseFlags
|
|
return fuse.OK
|
|
}
|
|
|
|
func (n *Inode) childrenAsDirstream() DirStream {
|
|
lst := n.childrenList()
|
|
r := make([]fuse.DirEntry, 0, len(lst))
|
|
for _, e := range lst {
|
|
r = append(r, fuse.DirEntry{Mode: e.Inode.Mode(),
|
|
Name: e.Name,
|
|
Ino: e.Inode.StableAttr().Ino})
|
|
}
|
|
return NewListDirStream(r)
|
|
}
|
|
|
|
func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status {
|
|
return b.readDirMaybeLookup(cancel, input, out, true)
|
|
}
|
|
|
|
func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status {
|
|
return b.readDirMaybeLookup(cancel, input, out, false)
|
|
}
|
|
|
|
func (b *rawBridge) readDirMaybeLookup(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList, lookup bool) fuse.Status {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
|
|
direnter, ok := f.file.(FileReaddirenter)
|
|
if !ok {
|
|
return fuse.OK
|
|
}
|
|
getdent := direnter.Readdirent
|
|
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
interruptedRead := false
|
|
if input.Offset != f.dirOffset {
|
|
// If the last readdir(plus) was interrupted, the
|
|
// kernel may consume just one entry from the readdir,
|
|
// and redo it.
|
|
for i, e := range f.lastRead {
|
|
if e.Off == input.Offset {
|
|
interruptedRead = true
|
|
todo := f.lastRead[i+1:]
|
|
todo = make([]fuse.DirEntry, len(todo))
|
|
copy(todo, f.lastRead[i+1:])
|
|
getdent = func(context.Context) (*fuse.DirEntry, syscall.Errno) {
|
|
if len(todo) > 0 {
|
|
de := &todo[0]
|
|
todo = todo[1:]
|
|
return de, 0
|
|
}
|
|
return nil, 0
|
|
}
|
|
f.dirOffset = input.Offset
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if input.Offset != f.dirOffset {
|
|
if sd, ok := f.file.(FileSeekdirer); ok {
|
|
errno := sd.Seekdir(ctx, input.Offset)
|
|
if errno != 0 {
|
|
return errnoToStatus(errno)
|
|
}
|
|
f.dirOffset = input.Offset
|
|
f.overflowErrno = 0
|
|
f.hasOverflow = false
|
|
} else {
|
|
return fuse.ENOTSUP
|
|
}
|
|
}
|
|
|
|
defer func() {
|
|
f.dirOffset = out.Offset
|
|
}()
|
|
|
|
first := true
|
|
f.lastRead = f.lastRead[:0]
|
|
for {
|
|
var de *fuse.DirEntry
|
|
var errno syscall.Errno
|
|
if f.hasOverflow && !interruptedRead {
|
|
f.hasOverflow = false
|
|
if f.overflowErrno != 0 {
|
|
return errnoToStatus(f.overflowErrno)
|
|
}
|
|
de = &f.overflow
|
|
} else {
|
|
de, errno = getdent(ctx)
|
|
if errno != 0 {
|
|
if first {
|
|
return errnoToStatus(errno)
|
|
} else {
|
|
f.hasOverflow = true
|
|
f.overflowErrno = errno
|
|
return fuse.OK
|
|
}
|
|
}
|
|
}
|
|
|
|
if de == nil {
|
|
break
|
|
}
|
|
|
|
first = false
|
|
if de.Off == 0 {
|
|
// This logic is dup from fuse.DirEntryList, but we need the offset here so it is part of lastRead
|
|
de.Off = out.Offset + 1
|
|
}
|
|
if !lookup {
|
|
if !out.AddDirEntry(*de) {
|
|
f.overflow = *de
|
|
f.hasOverflow = true
|
|
return fuse.OK
|
|
}
|
|
|
|
f.lastRead = append(f.lastRead, *de)
|
|
continue
|
|
}
|
|
|
|
entryOut := out.AddDirLookupEntry(*de)
|
|
if entryOut == nil {
|
|
f.overflow = *de
|
|
f.hasOverflow = true
|
|
return fuse.OK
|
|
}
|
|
f.lastRead = append(f.lastRead, *de)
|
|
|
|
// Virtual entries "." and ".." should be part of the
|
|
// directory listing, but not part of the filesystem tree.
|
|
// The values in EntryOut are ignored by Linux
|
|
// (see fuse_direntplus_link() in linux/fs/fuse/readdir.c), so leave
|
|
// them at zero-value.
|
|
if de.Name == "." || de.Name == ".." {
|
|
continue
|
|
}
|
|
|
|
child, errno := b.lookup(ctx, n, de.Name, entryOut)
|
|
if errno != 0 {
|
|
if b.options.NegativeTimeout != nil {
|
|
entryOut.SetEntryTimeout(*b.options.NegativeTimeout)
|
|
|
|
// TODO: maybe simply not produce the dirent here?
|
|
// test?
|
|
}
|
|
} else {
|
|
child, _ = b.addNewChild(n, de.Name, child, nil, 0, entryOut)
|
|
child.setEntryOut(entryOut)
|
|
b.setEntryOutTimeout(entryOut)
|
|
if de.Mode&syscall.S_IFMT != child.stableAttr.Mode&syscall.S_IFMT {
|
|
// The file type has changed behind our back. Use the new value.
|
|
out.FixMode(child.stableAttr.Mode)
|
|
}
|
|
entryOut.Mode = child.stableAttr.Mode | (entryOut.Mode & 07777)
|
|
}
|
|
}
|
|
|
|
return fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) FsyncDir(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status {
|
|
n, f := b.inode(input.NodeId, input.Fh)
|
|
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
|
|
if fsd, ok := f.file.(FileFsyncdirer); ok {
|
|
return errnoToStatus(fsd.Fsyncdir(ctx, input.FsyncFlags))
|
|
} else if fs, ok := n.ops.(NodeFsyncer); ok {
|
|
return errnoToStatus(fs.Fsync(ctx, f.file, input.FsyncFlags))
|
|
}
|
|
|
|
return fuse.ENOTSUP
|
|
}
|
|
|
|
func (b *rawBridge) StatFs(cancel <-chan struct{}, input *fuse.InHeader, out *fuse.StatfsOut) fuse.Status {
|
|
n, _ := b.inode(input.NodeId, 0)
|
|
if sf, ok := n.ops.(NodeStatfser); ok {
|
|
return errnoToStatus(sf.Statfs(&fuse.Context{Caller: input.Caller, Cancel: cancel}, out))
|
|
}
|
|
|
|
// leave zeroed out
|
|
return fuse.OK
|
|
}
|
|
|
|
func (b *rawBridge) Init(s *fuse.Server) {
|
|
b.server = s
|
|
}
|
|
|
|
func (b *rawBridge) CopyFileRange(cancel <-chan struct{}, in *fuse.CopyFileRangeIn) (size uint32, status fuse.Status) {
|
|
n1, f1 := b.inode(in.NodeId, in.FhIn)
|
|
cfr, ok := n1.ops.(NodeCopyFileRanger)
|
|
if !ok {
|
|
return 0, fuse.ENOTSUP
|
|
}
|
|
|
|
n2, f2 := b.inode(in.NodeIdOut, in.FhOut)
|
|
|
|
sz, errno := cfr.CopyFileRange(&fuse.Context{Caller: in.Caller, Cancel: cancel},
|
|
f1.file, in.OffIn, n2, f2.file, in.OffOut, in.Len, in.Flags)
|
|
return sz, errnoToStatus(errno)
|
|
}
|
|
|
|
func (b *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekOut) fuse.Status {
|
|
n, f := b.inode(in.NodeId, in.Fh)
|
|
|
|
ctx := &fuse.Context{Caller: in.Caller, Cancel: cancel}
|
|
|
|
ls, ok := n.ops.(NodeLseeker)
|
|
if ok {
|
|
off, errno := ls.Lseek(ctx,
|
|
f.file, in.Offset, in.Whence)
|
|
out.Offset = off
|
|
return errnoToStatus(errno)
|
|
}
|
|
if fs, ok := f.file.(FileLseeker); ok {
|
|
off, errno := fs.Lseek(ctx, in.Offset, in.Whence)
|
|
out.Offset = off
|
|
return errnoToStatus(errno)
|
|
}
|
|
var attr fuse.AttrOut
|
|
if s := b.getattr(ctx, n, nil, &attr); s != 0 {
|
|
return errnoToStatus(s)
|
|
}
|
|
if in.Whence == _SEEK_DATA {
|
|
if in.Offset >= attr.Size {
|
|
return errnoToStatus(syscall.ENXIO)
|
|
}
|
|
out.Offset = in.Offset
|
|
return fuse.OK
|
|
}
|
|
|
|
if in.Whence == _SEEK_HOLE {
|
|
if in.Offset > attr.Size {
|
|
return errnoToStatus(syscall.ENXIO)
|
|
}
|
|
out.Offset = attr.Size
|
|
return fuse.OK
|
|
}
|
|
|
|
return fuse.ENOTSUP
|
|
}
|