mirror of https://github.com/perkeep/perkeep.git
1216 lines
27 KiB
Go
1216 lines
27 KiB
Go
// FUSE service loop, for servers that wish to use it.
|
|
|
|
package fs
|
|
|
|
import (
|
|
"fmt"
|
|
"hash/fnv"
|
|
"io"
|
|
"path"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
import (
|
|
"camlistore.org/third_party/bazil.org/fuse"
|
|
"camlistore.org/third_party/bazil.org/fuse/fuseutil"
|
|
)
|
|
|
|
const (
|
|
attrValidTime = 1 * time.Minute
|
|
entryValidTime = 1 * time.Minute
|
|
)
|
|
|
|
// TODO: FINISH DOCS
|
|
|
|
// An Intr is a channel that signals that a request has been interrupted.
|
|
// Being able to receive from the channel means the request has been
|
|
// interrupted.
|
|
type Intr chan struct{}
|
|
|
|
func (Intr) String() string { return "fuse.Intr" }
|
|
|
|
// An FS is the interface required of a file system.
|
|
//
|
|
// Other FUSE requests can be handled by implementing methods from the
|
|
// FS* interfaces, for example FSIniter.
|
|
type FS interface {
|
|
// Root is called to obtain the Node for the file system root.
|
|
Root() (Node, fuse.Error)
|
|
}
|
|
|
|
type FSIniter interface {
|
|
// Init is called to initialize the FUSE connection.
|
|
// It can inspect the request and adjust the response as desired.
|
|
// The default response sets MaxReadahead to 0 and MaxWrite to 4096.
|
|
// Init must return promptly.
|
|
Init(*fuse.InitRequest, *fuse.InitResponse, Intr) fuse.Error
|
|
}
|
|
|
|
type FSStatfser interface {
|
|
// Statfs is called to obtain file system metadata.
|
|
// It should write that data to resp.
|
|
Statfs(*fuse.StatfsRequest, *fuse.StatfsResponse, Intr) fuse.Error
|
|
}
|
|
|
|
type FSDestroyer interface {
|
|
// Destroy is called when the file system is shutting down.
|
|
//
|
|
// Linux only sends this request for block device backed (fuseblk)
|
|
// filesystems, to allow them to flush writes to disk before the
|
|
// unmount completes.
|
|
//
|
|
// On normal FUSE filesystems, use Forget of the root Node to
|
|
// do actions at unmount time.
|
|
Destroy()
|
|
}
|
|
|
|
// A Node is the interface required of a file or directory.
|
|
// See the documentation for type FS for general information
|
|
// pertaining to all methods.
|
|
//
|
|
// Other FUSE requests can be handled by implementing methods from the
|
|
// Node* interfaces, for example NodeOpener.
|
|
type Node interface {
|
|
Attr() fuse.Attr
|
|
}
|
|
|
|
type NodeGetattrer interface {
|
|
// Getattr obtains the standard metadata for the receiver.
|
|
// It should store that metadata in resp.
|
|
//
|
|
// If this method is not implemented, the attributes will be
|
|
// generated based on Attr(), with zero values filled in.
|
|
Getattr(*fuse.GetattrRequest, *fuse.GetattrResponse, Intr) fuse.Error
|
|
}
|
|
|
|
type NodeSetattrer interface {
|
|
// Setattr sets the standard metadata for the receiver.
|
|
Setattr(*fuse.SetattrRequest, *fuse.SetattrResponse, Intr) fuse.Error
|
|
}
|
|
|
|
type NodeSymlinker interface {
|
|
// Symlink creates a new symbolic link in the receiver, which must be a directory.
|
|
//
|
|
// TODO is the above true about directories?
|
|
Symlink(*fuse.SymlinkRequest, Intr) (Node, fuse.Error)
|
|
}
|
|
|
|
// This optional request will be called only for symbolic link nodes.
|
|
type NodeReadlinker interface {
|
|
// Readlink reads a symbolic link.
|
|
Readlink(*fuse.ReadlinkRequest, Intr) (string, fuse.Error)
|
|
}
|
|
|
|
type NodeLinker interface {
|
|
// Link creates a new directory entry in the receiver based on an
|
|
// existing Node. Receiver must be a directory.
|
|
Link(r *fuse.LinkRequest, old Node, intr Intr) (Node, fuse.Error)
|
|
}
|
|
|
|
type NodeRemover interface {
|
|
// Remove removes the entry with the given name from
|
|
// the receiver, which must be a directory. The entry to be removed
|
|
// may correspond to a file (unlink) or to a directory (rmdir).
|
|
Remove(*fuse.RemoveRequest, Intr) fuse.Error
|
|
}
|
|
|
|
type NodeAccesser interface {
|
|
// Access checks whether the calling context has permission for
|
|
// the given operations on the receiver. If so, Access should
|
|
// return nil. If not, Access should return EPERM.
|
|
//
|
|
// Note that this call affects the result of the access(2) system
|
|
// call but not the open(2) system call. If Access is not
|
|
// implemented, the Node behaves as if it always returns nil
|
|
// (permission granted), relying on checks in Open instead.
|
|
Access(*fuse.AccessRequest, Intr) fuse.Error
|
|
}
|
|
|
|
type NodeStringLookuper interface {
|
|
// Lookup looks up a specific entry in the receiver,
|
|
// which must be a directory. Lookup should return a Node
|
|
// corresponding to the entry. If the name does not exist in
|
|
// the directory, Lookup should return nil, err.
|
|
//
|
|
// Lookup need not to handle the names "." and "..".
|
|
Lookup(string, Intr) (Node, fuse.Error)
|
|
}
|
|
|
|
type NodeRequestLookuper interface {
|
|
// Lookup looks up a specific entry in the receiver.
|
|
// See NodeStringLookuper for more.
|
|
Lookup(*fuse.LookupRequest, *fuse.LookupResponse, Intr) (Node, fuse.Error)
|
|
}
|
|
|
|
type NodeMkdirer interface {
|
|
Mkdir(*fuse.MkdirRequest, Intr) (Node, fuse.Error)
|
|
}
|
|
|
|
type NodeOpener interface {
|
|
// Open opens the receiver.
|
|
// XXX note about access. XXX OpenFlags.
|
|
// XXX note that the Node may be a file or directory.
|
|
Open(*fuse.OpenRequest, *fuse.OpenResponse, Intr) (Handle, fuse.Error)
|
|
}
|
|
|
|
type NodeCreater interface {
|
|
// Create creates a new directory entry in the receiver, which
|
|
// must be a directory.
|
|
Create(*fuse.CreateRequest, *fuse.CreateResponse, Intr) (Node, Handle, fuse.Error)
|
|
}
|
|
|
|
type NodeForgetter interface {
|
|
Forget()
|
|
}
|
|
|
|
type NodeRenamer interface {
|
|
Rename(r *fuse.RenameRequest, newDir Node, intr Intr) fuse.Error
|
|
}
|
|
|
|
type NodeMknoder interface {
|
|
Mknod(r *fuse.MknodRequest, intr Intr) (Node, fuse.Error)
|
|
}
|
|
|
|
// TODO this should be on Handle not Node
|
|
type NodeFsyncer interface {
|
|
Fsync(r *fuse.FsyncRequest, intr Intr) fuse.Error
|
|
}
|
|
|
|
type NodeGetxattrer interface {
|
|
// Getxattr gets an extended attribute by the given name from the
|
|
// node.
|
|
//
|
|
// If there is no xattr by that name, returns fuse.ENODATA. This
|
|
// will be translated to the platform-specific correct error code
|
|
// by the framework.
|
|
Getxattr(req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse, intr Intr) fuse.Error
|
|
}
|
|
|
|
type NodeListxattrer interface {
|
|
// Listxattr lists the extended attributes recorded for the node.
|
|
Listxattr(req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse, intr Intr) fuse.Error
|
|
}
|
|
|
|
type NodeSetxattrer interface {
|
|
// Setxattr sets an extended attribute with the given name and
|
|
// value for the node.
|
|
Setxattr(req *fuse.SetxattrRequest, intr Intr) fuse.Error
|
|
}
|
|
|
|
type NodeRemovexattrer interface {
|
|
// Removexattr removes an extended attribute for the name.
|
|
//
|
|
// If there is no xattr by that name, returns fuse.ENODATA. This
|
|
// will be translated to the platform-specific correct error code
|
|
// by the framework.
|
|
Removexattr(req *fuse.RemovexattrRequest, intr Intr) fuse.Error
|
|
}
|
|
|
|
var startTime = time.Now()
|
|
|
|
func nodeAttr(n Node) (attr fuse.Attr) {
|
|
attr = n.Attr()
|
|
if attr.Nlink == 0 {
|
|
attr.Nlink = 1
|
|
}
|
|
if attr.Atime.IsZero() {
|
|
attr.Atime = startTime
|
|
}
|
|
if attr.Mtime.IsZero() {
|
|
attr.Mtime = startTime
|
|
}
|
|
if attr.Ctime.IsZero() {
|
|
attr.Ctime = startTime
|
|
}
|
|
if attr.Crtime.IsZero() {
|
|
attr.Crtime = startTime
|
|
}
|
|
return
|
|
}
|
|
|
|
// A Handle is the interface required of an opened file or directory.
|
|
// See the documentation for type FS for general information
|
|
// pertaining to all methods.
|
|
//
|
|
// Other FUSE requests can be handled by implementing methods from the
|
|
// Node* interfaces. The most common to implement are
|
|
// HandleReader, HandleReadDirer, and HandleWriter.
|
|
//
|
|
// TODO implement methods: Getlk, Setlk, Setlkw
|
|
type Handle interface {
|
|
}
|
|
|
|
type HandleFlusher interface {
|
|
// Flush is called each time the file or directory is closed.
|
|
// Because there can be multiple file descriptors referring to a
|
|
// single opened file, Flush can be called multiple times.
|
|
Flush(*fuse.FlushRequest, Intr) fuse.Error
|
|
}
|
|
|
|
type HandleReadAller interface {
|
|
ReadAll(Intr) ([]byte, fuse.Error)
|
|
}
|
|
|
|
type HandleReadDirer interface {
|
|
ReadDir(Intr) ([]fuse.Dirent, fuse.Error)
|
|
}
|
|
|
|
type HandleReader interface {
|
|
Read(*fuse.ReadRequest, *fuse.ReadResponse, Intr) fuse.Error
|
|
}
|
|
|
|
type HandleWriter interface {
|
|
Write(*fuse.WriteRequest, *fuse.WriteResponse, Intr) fuse.Error
|
|
}
|
|
|
|
type HandleReleaser interface {
|
|
Release(*fuse.ReleaseRequest, Intr) fuse.Error
|
|
}
|
|
|
|
// Serve serves the FUSE connection by making calls to the methods
|
|
// of fs and the Nodes and Handles it makes available. It returns only
|
|
// when the connection has been closed or an unexpected error occurs.
|
|
func Serve(c *fuse.Conn, fs FS) error {
|
|
sc := serveConn{}
|
|
sc.req = map[fuse.RequestID]*serveRequest{}
|
|
|
|
root, err := fs.Root()
|
|
if err != nil {
|
|
return fmt.Errorf("cannot obtain root node: %v", syscall.Errno(err.(fuse.Errno)).Error())
|
|
}
|
|
sc.node = append(sc.node, nil, &serveNode{name: "/", node: root, refs: 1})
|
|
sc.handle = append(sc.handle, nil)
|
|
|
|
for {
|
|
req, err := c.ReadRequest()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
|
|
go sc.serve(fs, req)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type nothing struct{}
|
|
|
|
type serveConn struct {
|
|
meta sync.Mutex
|
|
req map[fuse.RequestID]*serveRequest
|
|
node []*serveNode
|
|
handle []*serveHandle
|
|
freeNode []fuse.NodeID
|
|
freeHandle []fuse.HandleID
|
|
nodeGen uint64
|
|
}
|
|
|
|
type serveRequest struct {
|
|
Request fuse.Request
|
|
Intr Intr
|
|
}
|
|
|
|
type serveNode struct {
|
|
name string
|
|
node Node
|
|
refs uint64
|
|
}
|
|
|
|
func (sn *serveNode) attr() (attr fuse.Attr) {
|
|
attr = nodeAttr(sn.node)
|
|
if attr.Inode == 0 {
|
|
attr.Inode = hash(sn.name)
|
|
}
|
|
return
|
|
}
|
|
|
|
func hash(s string) uint64 {
|
|
f := fnv.New64()
|
|
f.Write([]byte(s))
|
|
return f.Sum64()
|
|
}
|
|
|
|
type serveHandle struct {
|
|
handle Handle
|
|
readData []byte
|
|
nodeID fuse.NodeID
|
|
}
|
|
|
|
// NodeRef can be embedded in a Node to recognize the same Node being
|
|
// returned from multiple Lookup, Create etc calls.
|
|
//
|
|
// Without this, each Node will get a new NodeID, causing spurious
|
|
// cache invalidations, extra lookups and aliasing anomalies. This may
|
|
// not matter for a simple, read-only filesystem.
|
|
type NodeRef struct {
|
|
id fuse.NodeID
|
|
generation uint64
|
|
}
|
|
|
|
// nodeRef is only ever accessed while holding serveConn.meta
|
|
func (n *NodeRef) nodeRef() *NodeRef {
|
|
return n
|
|
}
|
|
|
|
type nodeRef interface {
|
|
nodeRef() *NodeRef
|
|
}
|
|
|
|
func (c *serveConn) saveNode(name string, node Node) (id fuse.NodeID, gen uint64, sn *serveNode) {
|
|
c.meta.Lock()
|
|
defer c.meta.Unlock()
|
|
|
|
var ref *NodeRef
|
|
if nodeRef, ok := node.(nodeRef); ok {
|
|
ref = nodeRef.nodeRef()
|
|
|
|
if ref.id != 0 {
|
|
// dropNode guarantees that NodeRef is zeroed at the same
|
|
// time as the NodeID is removed from serveConn.node, as
|
|
// guarded by c.meta; this means sn cannot be nil here
|
|
sn = c.node[ref.id]
|
|
sn.refs++
|
|
return ref.id, ref.generation, sn
|
|
}
|
|
}
|
|
|
|
sn = &serveNode{name: name, node: node, refs: 1}
|
|
if n := len(c.freeNode); n > 0 {
|
|
id = c.freeNode[n-1]
|
|
c.freeNode = c.freeNode[:n-1]
|
|
c.node[id] = sn
|
|
c.nodeGen++
|
|
} else {
|
|
id = fuse.NodeID(len(c.node))
|
|
c.node = append(c.node, sn)
|
|
}
|
|
gen = c.nodeGen
|
|
if ref != nil {
|
|
ref.id = id
|
|
ref.generation = gen
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *serveConn) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) {
|
|
c.meta.Lock()
|
|
shandle := &serveHandle{handle: handle, nodeID: nodeID}
|
|
if n := len(c.freeHandle); n > 0 {
|
|
id = c.freeHandle[n-1]
|
|
c.freeHandle = c.freeHandle[:n-1]
|
|
c.handle[id] = shandle
|
|
} else {
|
|
id = fuse.HandleID(len(c.handle))
|
|
c.handle = append(c.handle, shandle)
|
|
}
|
|
c.meta.Unlock()
|
|
return
|
|
}
|
|
|
|
type nodeRefcountDropBug struct {
|
|
N uint64
|
|
Refs uint64
|
|
Node fuse.NodeID
|
|
}
|
|
|
|
func (n *nodeRefcountDropBug) String() string {
|
|
return fmt.Sprintf("bug: trying to drop %d of %d references to %v", n.N, n.Refs, n.Node)
|
|
}
|
|
|
|
func (c *serveConn) dropNode(id fuse.NodeID, n uint64) (forget bool) {
|
|
c.meta.Lock()
|
|
defer c.meta.Unlock()
|
|
snode := c.node[id]
|
|
|
|
if snode == nil {
|
|
// this should only happen if refcounts kernel<->us disagree
|
|
// *and* two ForgetRequests for the same node race each other;
|
|
// this indicates a bug somewhere
|
|
fuse.Debug(nodeRefcountDropBug{N: n, Node: id})
|
|
|
|
// we may end up triggering Forget twice, but that's better
|
|
// than not even once, and that's the best we can do
|
|
return true
|
|
}
|
|
|
|
if n > snode.refs {
|
|
fuse.Debug(nodeRefcountDropBug{N: n, Refs: snode.refs, Node: id})
|
|
n = snode.refs
|
|
}
|
|
|
|
snode.refs -= n
|
|
if snode.refs == 0 {
|
|
c.node[id] = nil
|
|
if nodeRef, ok := snode.node.(nodeRef); ok {
|
|
ref := nodeRef.nodeRef()
|
|
*ref = NodeRef{}
|
|
}
|
|
c.freeNode = append(c.freeNode, id)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *serveConn) dropHandle(id fuse.HandleID) {
|
|
c.meta.Lock()
|
|
c.handle[id] = nil
|
|
c.freeHandle = append(c.freeHandle, id)
|
|
c.meta.Unlock()
|
|
}
|
|
|
|
type missingHandle struct {
|
|
Handle fuse.HandleID
|
|
MaxHandle fuse.HandleID
|
|
}
|
|
|
|
func (m missingHandle) String() string {
|
|
return fmt.Sprint("missing handle", m.Handle, m.MaxHandle)
|
|
}
|
|
|
|
// Returns nil for invalid handles.
|
|
func (c *serveConn) getHandle(id fuse.HandleID) (shandle *serveHandle) {
|
|
c.meta.Lock()
|
|
defer c.meta.Unlock()
|
|
if id < fuse.HandleID(len(c.handle)) {
|
|
shandle = c.handle[uint(id)]
|
|
}
|
|
if shandle == nil {
|
|
fuse.Debug(missingHandle{
|
|
Handle: id,
|
|
MaxHandle: fuse.HandleID(len(c.handle)),
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
type request struct {
|
|
Op string
|
|
Request *fuse.Header
|
|
In interface{} `json:",omitempty"`
|
|
}
|
|
|
|
func (r request) String() string {
|
|
return fmt.Sprintf("<- %s", r.In)
|
|
}
|
|
|
|
type logResponseHeader struct {
|
|
ID fuse.RequestID
|
|
}
|
|
|
|
func (m logResponseHeader) String() string {
|
|
return fmt.Sprintf("ID=%#x", m.ID)
|
|
}
|
|
|
|
type response struct {
|
|
Op string
|
|
Request logResponseHeader
|
|
Out interface{} `json:",omitempty"`
|
|
Error fuse.Error `json:",omitempty"`
|
|
}
|
|
|
|
func (r response) String() string {
|
|
switch {
|
|
case r.Error != nil && r.Out != nil:
|
|
return fmt.Sprintf("-> %s error=%s %s", r.Request, r.Error, r.Out)
|
|
case r.Error != nil:
|
|
return fmt.Sprintf("-> %s error=%s", r.Request, r.Error)
|
|
case r.Out != nil:
|
|
return fmt.Sprintf("-> %s %s", r.Request, r.Out)
|
|
default:
|
|
return fmt.Sprintf("-> %s", r.Request)
|
|
}
|
|
}
|
|
|
|
type logMissingNode struct {
|
|
MaxNode fuse.NodeID
|
|
}
|
|
|
|
func opName(req fuse.Request) string {
|
|
t := reflect.Indirect(reflect.ValueOf(req)).Type()
|
|
s := t.Name()
|
|
s = strings.TrimSuffix(s, "Request")
|
|
return s
|
|
}
|
|
|
|
type logLinkRequestOldNodeNotFound struct {
|
|
Request *fuse.Header
|
|
In *fuse.LinkRequest
|
|
}
|
|
|
|
func (m *logLinkRequestOldNodeNotFound) String() string {
|
|
return fmt.Sprintf("In LinkRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.OldNode)
|
|
}
|
|
|
|
type renameNewDirNodeNotFound struct {
|
|
Request *fuse.Header
|
|
In *fuse.RenameRequest
|
|
}
|
|
|
|
func (m *renameNewDirNodeNotFound) String() string {
|
|
return fmt.Sprintf("In RenameRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.NewDir)
|
|
}
|
|
|
|
func (c *serveConn) serve(fs FS, r fuse.Request) {
|
|
intr := make(Intr)
|
|
req := &serveRequest{Request: r, Intr: intr}
|
|
|
|
fuse.Debug(request{
|
|
Op: opName(r),
|
|
Request: r.Hdr(),
|
|
In: r,
|
|
})
|
|
var node Node
|
|
var snode *serveNode
|
|
c.meta.Lock()
|
|
hdr := r.Hdr()
|
|
if id := hdr.Node; id != 0 {
|
|
if id < fuse.NodeID(len(c.node)) {
|
|
snode = c.node[uint(id)]
|
|
}
|
|
if snode == nil {
|
|
c.meta.Unlock()
|
|
fuse.Debug(response{
|
|
Op: opName(r),
|
|
Request: logResponseHeader{ID: hdr.ID},
|
|
Error: fuse.ESTALE,
|
|
// this is the only place that sets both Error and
|
|
// Out; not sure if i want to do that; might get rid
|
|
// of len(c.node) things altogether
|
|
Out: logMissingNode{
|
|
MaxNode: fuse.NodeID(len(c.node)),
|
|
},
|
|
})
|
|
r.RespondError(fuse.ESTALE)
|
|
return
|
|
}
|
|
node = snode.node
|
|
}
|
|
if c.req[hdr.ID] != nil {
|
|
// This happens with OSXFUSE. Assume it's okay and
|
|
// that we'll never see an interrupt for this one.
|
|
// Otherwise everything wedges. TODO: Report to OSXFUSE?
|
|
//
|
|
// TODO this might have been because of missing done() calls
|
|
intr = nil
|
|
} else {
|
|
c.req[hdr.ID] = req
|
|
}
|
|
c.meta.Unlock()
|
|
|
|
// Call this before responding.
|
|
// After responding is too late: we might get another request
|
|
// with the same ID and be very confused.
|
|
done := func(resp interface{}) {
|
|
msg := response{
|
|
Op: opName(r),
|
|
Request: logResponseHeader{ID: hdr.ID},
|
|
}
|
|
if err, ok := resp.(fuse.Error); ok {
|
|
msg.Error = err
|
|
} else {
|
|
msg.Out = resp
|
|
}
|
|
fuse.Debug(msg)
|
|
|
|
c.meta.Lock()
|
|
delete(c.req, hdr.ID)
|
|
c.meta.Unlock()
|
|
}
|
|
|
|
switch r := r.(type) {
|
|
default:
|
|
// Note: To FUSE, ENOSYS means "this server never implements this request."
|
|
// It would be inappropriate to return ENOSYS for other operations in this
|
|
// switch that might only be unavailable in some contexts, not all.
|
|
done(fuse.ENOSYS)
|
|
r.RespondError(fuse.ENOSYS)
|
|
|
|
// FS operations.
|
|
case *fuse.InitRequest:
|
|
s := &fuse.InitResponse{
|
|
MaxWrite: 4096,
|
|
}
|
|
if fs, ok := fs.(FSIniter); ok {
|
|
if err := fs.Init(r, s, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
}
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.StatfsRequest:
|
|
s := &fuse.StatfsResponse{}
|
|
if fs, ok := fs.(FSStatfser); ok {
|
|
if err := fs.Statfs(r, s, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
}
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
// Node operations.
|
|
case *fuse.GetattrRequest:
|
|
s := &fuse.GetattrResponse{}
|
|
if n, ok := node.(NodeGetattrer); ok {
|
|
if err := n.Getattr(r, s, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
} else {
|
|
s.AttrValid = attrValidTime
|
|
s.Attr = snode.attr()
|
|
}
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.SetattrRequest:
|
|
s := &fuse.SetattrResponse{}
|
|
if n, ok := node.(NodeSetattrer); ok {
|
|
if err := n.Setattr(r, s, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
done(s)
|
|
r.Respond(s)
|
|
break
|
|
}
|
|
|
|
if s.AttrValid == 0 {
|
|
s.AttrValid = attrValidTime
|
|
}
|
|
s.Attr = snode.attr()
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.SymlinkRequest:
|
|
s := &fuse.SymlinkResponse{}
|
|
n, ok := node.(NodeSymlinker)
|
|
if !ok {
|
|
done(fuse.EIO) // XXX or EPERM like Mkdir?
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
n2, err := n.Symlink(r, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
c.saveLookup(&s.LookupResponse, snode, r.NewName, n2)
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.ReadlinkRequest:
|
|
n, ok := node.(NodeReadlinker)
|
|
if !ok {
|
|
done(fuse.EIO) /// XXX or EPERM?
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
target, err := n.Readlink(r, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
done(target)
|
|
r.Respond(target)
|
|
|
|
case *fuse.LinkRequest:
|
|
n, ok := node.(NodeLinker)
|
|
if !ok {
|
|
done(fuse.EIO) /// XXX or EPERM?
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
c.meta.Lock()
|
|
var oldNode *serveNode
|
|
if int(r.OldNode) < len(c.node) {
|
|
oldNode = c.node[r.OldNode]
|
|
}
|
|
c.meta.Unlock()
|
|
if oldNode == nil {
|
|
fuse.Debug(logLinkRequestOldNodeNotFound{
|
|
Request: r.Hdr(),
|
|
In: r,
|
|
})
|
|
done(fuse.EIO)
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
n2, err := n.Link(r, oldNode.node, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
s := &fuse.LookupResponse{}
|
|
c.saveLookup(s, snode, r.NewName, n2)
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.RemoveRequest:
|
|
n, ok := node.(NodeRemover)
|
|
if !ok {
|
|
done(fuse.EIO) /// XXX or EPERM?
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
err := n.Remove(r, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.AccessRequest:
|
|
if n, ok := node.(NodeAccesser); ok {
|
|
if err := n.Access(r, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.LookupRequest:
|
|
var n2 Node
|
|
var err fuse.Error
|
|
s := &fuse.LookupResponse{}
|
|
if n, ok := node.(NodeStringLookuper); ok {
|
|
n2, err = n.Lookup(r.Name, intr)
|
|
} else if n, ok := node.(NodeRequestLookuper); ok {
|
|
n2, err = n.Lookup(r, s, intr)
|
|
} else {
|
|
done(fuse.ENOENT)
|
|
r.RespondError(fuse.ENOENT)
|
|
break
|
|
}
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
c.saveLookup(s, snode, r.Name, n2)
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.MkdirRequest:
|
|
s := &fuse.MkdirResponse{}
|
|
n, ok := node.(NodeMkdirer)
|
|
if !ok {
|
|
done(fuse.EPERM)
|
|
r.RespondError(fuse.EPERM)
|
|
break
|
|
}
|
|
n2, err := n.Mkdir(r, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
c.saveLookup(&s.LookupResponse, snode, r.Name, n2)
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.OpenRequest:
|
|
s := &fuse.OpenResponse{Flags: fuse.OpenDirectIO}
|
|
var h2 Handle
|
|
if n, ok := node.(NodeOpener); ok {
|
|
hh, err := n.Open(r, s, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
h2 = hh
|
|
} else {
|
|
h2 = node
|
|
}
|
|
s.Handle = c.saveHandle(h2, hdr.Node)
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.CreateRequest:
|
|
n, ok := node.(NodeCreater)
|
|
if !ok {
|
|
// If we send back ENOSYS, FUSE will try mknod+open.
|
|
done(fuse.EPERM)
|
|
r.RespondError(fuse.EPERM)
|
|
break
|
|
}
|
|
s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{Flags: fuse.OpenDirectIO}}
|
|
n2, h2, err := n.Create(r, s, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
c.saveLookup(&s.LookupResponse, snode, r.Name, n2)
|
|
s.Handle = c.saveHandle(h2, hdr.Node)
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.GetxattrRequest:
|
|
n, ok := node.(NodeGetxattrer)
|
|
if !ok {
|
|
done(fuse.ENOTSUP)
|
|
r.RespondError(fuse.ENOTSUP)
|
|
break
|
|
}
|
|
s := &fuse.GetxattrResponse{}
|
|
err := n.Getxattr(r, s, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
|
|
done(fuse.ERANGE)
|
|
r.RespondError(fuse.ERANGE)
|
|
break
|
|
}
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.ListxattrRequest:
|
|
n, ok := node.(NodeListxattrer)
|
|
if !ok {
|
|
done(fuse.ENOTSUP)
|
|
r.RespondError(fuse.ENOTSUP)
|
|
break
|
|
}
|
|
s := &fuse.ListxattrResponse{}
|
|
err := n.Listxattr(r, s, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
|
|
done(fuse.ERANGE)
|
|
r.RespondError(fuse.ERANGE)
|
|
break
|
|
}
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.SetxattrRequest:
|
|
n, ok := node.(NodeSetxattrer)
|
|
if !ok {
|
|
done(fuse.ENOTSUP)
|
|
r.RespondError(fuse.ENOTSUP)
|
|
break
|
|
}
|
|
err := n.Setxattr(r, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.RemovexattrRequest:
|
|
n, ok := node.(NodeRemovexattrer)
|
|
if !ok {
|
|
done(fuse.ENOTSUP)
|
|
r.RespondError(fuse.ENOTSUP)
|
|
break
|
|
}
|
|
err := n.Removexattr(r, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.ForgetRequest:
|
|
forget := c.dropNode(hdr.Node, r.N)
|
|
if forget {
|
|
n, ok := node.(NodeForgetter)
|
|
if ok {
|
|
n.Forget()
|
|
}
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
// Handle operations.
|
|
case *fuse.ReadRequest:
|
|
shandle := c.getHandle(r.Handle)
|
|
if shandle == nil {
|
|
done(fuse.ESTALE)
|
|
r.RespondError(fuse.ESTALE)
|
|
return
|
|
}
|
|
handle := shandle.handle
|
|
|
|
s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)}
|
|
if r.Dir {
|
|
if h, ok := handle.(HandleReadDirer); ok {
|
|
if shandle.readData == nil {
|
|
dirs, err := h.ReadDir(intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
var data []byte
|
|
for _, dir := range dirs {
|
|
if dir.Inode == 0 {
|
|
dir.Inode = hash(path.Join(snode.name, dir.Name))
|
|
}
|
|
data = fuse.AppendDirent(data, dir)
|
|
}
|
|
shandle.readData = data
|
|
}
|
|
fuseutil.HandleRead(r, s, shandle.readData)
|
|
done(s)
|
|
r.Respond(s)
|
|
break
|
|
}
|
|
} else {
|
|
if h, ok := handle.(HandleReadAller); ok {
|
|
if shandle.readData == nil {
|
|
data, err := h.ReadAll(intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
if data == nil {
|
|
data = []byte{}
|
|
}
|
|
shandle.readData = data
|
|
}
|
|
fuseutil.HandleRead(r, s, shandle.readData)
|
|
done(s)
|
|
r.Respond(s)
|
|
break
|
|
}
|
|
h, ok := handle.(HandleReader)
|
|
if !ok {
|
|
fmt.Printf("NO READ FOR %T\n", handle)
|
|
done(fuse.EIO)
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
if err := h.Read(r, s, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
}
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.WriteRequest:
|
|
shandle := c.getHandle(r.Handle)
|
|
if shandle == nil {
|
|
done(fuse.ESTALE)
|
|
r.RespondError(fuse.ESTALE)
|
|
return
|
|
}
|
|
|
|
s := &fuse.WriteResponse{}
|
|
if h, ok := shandle.handle.(HandleWriter); ok {
|
|
if err := h.Write(r, s, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
done(s)
|
|
r.Respond(s)
|
|
break
|
|
}
|
|
done(fuse.EIO)
|
|
r.RespondError(fuse.EIO)
|
|
|
|
case *fuse.FlushRequest:
|
|
shandle := c.getHandle(r.Handle)
|
|
if shandle == nil {
|
|
done(fuse.ESTALE)
|
|
r.RespondError(fuse.ESTALE)
|
|
return
|
|
}
|
|
handle := shandle.handle
|
|
|
|
if h, ok := handle.(HandleFlusher); ok {
|
|
if err := h.Flush(r, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.ReleaseRequest:
|
|
shandle := c.getHandle(r.Handle)
|
|
if shandle == nil {
|
|
done(fuse.ESTALE)
|
|
r.RespondError(fuse.ESTALE)
|
|
return
|
|
}
|
|
handle := shandle.handle
|
|
|
|
// No matter what, release the handle.
|
|
c.dropHandle(r.Handle)
|
|
|
|
if h, ok := handle.(HandleReleaser); ok {
|
|
if err := h.Release(r, intr); err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.DestroyRequest:
|
|
if fs, ok := fs.(FSDestroyer); ok {
|
|
fs.Destroy()
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.RenameRequest:
|
|
c.meta.Lock()
|
|
var newDirNode *serveNode
|
|
if int(r.NewDir) < len(c.node) {
|
|
newDirNode = c.node[r.NewDir]
|
|
}
|
|
c.meta.Unlock()
|
|
if newDirNode == nil {
|
|
fuse.Debug(renameNewDirNodeNotFound{
|
|
Request: r.Hdr(),
|
|
In: r,
|
|
})
|
|
done(fuse.EIO)
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
n, ok := node.(NodeRenamer)
|
|
if !ok {
|
|
done(fuse.EIO) // XXX or EPERM like Mkdir?
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
err := n.Rename(r, newDirNode.node, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.MknodRequest:
|
|
n, ok := node.(NodeMknoder)
|
|
if !ok {
|
|
done(fuse.EIO)
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
n2, err := n.Mknod(r, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
s := &fuse.LookupResponse{}
|
|
c.saveLookup(s, snode, r.Name, n2)
|
|
done(s)
|
|
r.Respond(s)
|
|
|
|
case *fuse.FsyncRequest:
|
|
n, ok := node.(NodeFsyncer)
|
|
if !ok {
|
|
done(fuse.EIO)
|
|
r.RespondError(fuse.EIO)
|
|
break
|
|
}
|
|
err := n.Fsync(r, intr)
|
|
if err != nil {
|
|
done(err)
|
|
r.RespondError(err)
|
|
break
|
|
}
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
case *fuse.InterruptRequest:
|
|
c.meta.Lock()
|
|
ireq := c.req[r.IntrID]
|
|
if ireq != nil && ireq.Intr != nil {
|
|
close(ireq.Intr)
|
|
ireq.Intr = nil
|
|
}
|
|
c.meta.Unlock()
|
|
done(nil)
|
|
r.Respond()
|
|
|
|
/* case *FsyncdirRequest:
|
|
done(ENOSYS)
|
|
r.RespondError(ENOSYS)
|
|
|
|
case *GetlkRequest, *SetlkRequest, *SetlkwRequest:
|
|
done(ENOSYS)
|
|
r.RespondError(ENOSYS)
|
|
|
|
case *BmapRequest:
|
|
done(ENOSYS)
|
|
r.RespondError(ENOSYS)
|
|
|
|
case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest:
|
|
done(ENOSYS)
|
|
r.RespondError(ENOSYS)
|
|
*/
|
|
}
|
|
}
|
|
|
|
func (c *serveConn) saveLookup(s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) {
|
|
name := path.Join(snode.name, elem)
|
|
var sn *serveNode
|
|
s.Node, s.Generation, sn = c.saveNode(name, n2)
|
|
if s.EntryValid == 0 {
|
|
s.EntryValid = entryValidTime
|
|
}
|
|
if s.AttrValid == 0 {
|
|
s.AttrValid = attrValidTime
|
|
}
|
|
s.Attr = sn.attr()
|
|
}
|
|
|
|
// DataHandle returns a read-only Handle that satisfies reads
|
|
// using the given data.
|
|
func DataHandle(data []byte) Handle {
|
|
return &dataHandle{data}
|
|
}
|
|
|
|
type dataHandle struct {
|
|
data []byte
|
|
}
|
|
|
|
func (d *dataHandle) ReadAll(intr Intr) ([]byte, fuse.Error) {
|
|
return d.data, nil
|
|
}
|