Refactored the way the user obtains contexts and replies to ops.

The new interface gives much much cleaner documentation for package
fuseops, which is what we mostly look at. In particular, there isn't a
ton of stuttering in the method listings.

The change also simplifies the implementation of package fuseops, which
was becoming super unwieldy.
geesefs-0-30-9
Aaron Jacobs 2015-07-27 15:56:27 +10:00
commit 83630d4268
15 changed files with 525 additions and 786 deletions

View File

@ -26,11 +26,14 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/fuse/internal/fusekernel" "github.com/jacobsa/fuse/internal/fusekernel"
) )
type contextKeyType uint64
const contextKey contextKeyType = 0
// Ask the Linux kernel for larger read requests. // Ask the Linux kernel for larger read requests.
// //
// As of 2015-03-26, the behavior in the kernel is: // As of 2015-03-26, the behavior in the kernel is:
@ -82,6 +85,14 @@ type Connection struct {
cancelFuncs map[uint64]func() cancelFuncs map[uint64]func()
} }
// State that is maintained for each in-flight op. This is stuffed into the
// context that the user uses to reply to the op.
type opState struct {
inMsg *buffer.InMessage
op interface{}
opID uint32 // For logging
}
// Create a connection wrapping the supplied file descriptor connected to the // Create a connection wrapping the supplied file descriptor connected to the
// kernel. You must eventually call c.close(). // kernel. You must eventually call c.close().
// //
@ -113,15 +124,16 @@ func newConnection(
// Do the work necessary to cause the mount process to complete. // Do the work necessary to cause the mount process to complete.
func (c *Connection) Init() (err error) { func (c *Connection) Init() (err error) {
// Read the init op. // Read the init op.
op, err := c.ReadOp() ctx, op, err := c.ReadOp()
if err != nil { if err != nil {
err = fmt.Errorf("Reading init op: %v", err) err = fmt.Errorf("Reading init op: %v", err)
return return
} }
initOp, ok := op.(*fuseops.InternalInitOp) initOp, ok := op.(*initOp)
if !ok { if !ok {
err = fmt.Errorf("Expected *fuseops.InternalInitOp, got %T", op) c.Reply(ctx, syscall.EPROTO)
err = fmt.Errorf("Expected *initOp, got %T", op)
return return
} }
@ -132,7 +144,7 @@ func (c *Connection) Init() (err error) {
} }
if initOp.Kernel.LT(min) { if initOp.Kernel.LT(min) {
initOp.Respond(syscall.EPROTO) c.Reply(ctx, syscall.EPROTO)
err = fmt.Errorf("Version too old: %v", initOp.Kernel) err = fmt.Errorf("Version too old: %v", initOp.Kernel)
return return
} }
@ -152,8 +164,8 @@ func (c *Connection) Init() (err error) {
initOp.MaxReadahead = maxReadahead initOp.MaxReadahead = maxReadahead
initOp.MaxWrite = buffer.MaxWriteSize initOp.MaxWrite = buffer.MaxWriteSize
initOp.Flags = fusekernel.InitBigWrites initOp.Flags = fusekernel.InitBigWrites
initOp.Respond(nil)
c.Reply(ctx, nil)
return return
} }
@ -372,14 +384,18 @@ func (c *Connection) writeMessage(msg []byte) (err error) {
return return
} }
// Read the next op from the kernel process. Return io.EOF if the kernel has // Read the next op from the kernel process, returning the op and a context
// closed the connection. // that should be used for work related to the op. Return io.EOF if the kernel
// has closed the connection.
//
// If err != nil, the user is responsible for later calling c.Reply with the
// returned context.
// //
// This function delivers ops in exactly the order they are received from // This function delivers ops in exactly the order they are received from
// /dev/fuse. It must not be called multiple times concurrently. // /dev/fuse. It must not be called multiple times concurrently.
// //
// LOCKS_EXCLUDED(c.mu) // LOCKS_EXCLUDED(c.mu)
func (c *Connection) ReadOp() (op fuseops.Op, err error) { func (c *Connection) ReadOp() (ctx context.Context, op interface{}, err error) {
// Keep going until we find a request we know how to convert. // Keep going until we find a request we know how to convert.
for { for {
// Read the next message from the kernel. // Read the next message from the kernel.
@ -389,90 +405,91 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) {
return return
} }
// Choose an ID for this operation for the purposes of logging. // Convert the message to an op.
op, err = convertInMessage(m, c.protocol)
if err != nil {
err = fmt.Errorf("convertInMessage: %v", err)
return
}
// Choose an ID for this operation for the purposes of logging, and log it.
opID := c.nextOpID opID := c.nextOpID
c.nextOpID++ c.nextOpID++
// Set up op dependencies. c.debugLog(opID, 1, "<- %#v", op)
opCtx := c.beginOp(m.Header().Opcode, m.Header().Unique)
var debugLogForOp func(int, string, ...interface{}) // Special case: handle interrupt requests inline.
if c.debugLogger != nil { if interruptOp, ok := op.(*interruptOp); ok {
debugLogForOp = func(calldepth int, format string, v ...interface{}) {
c.debugLog(opID, calldepth+1, format, v...)
}
}
sendReply := func(
op fuseops.Op,
fuseID uint64,
replyMsg []byte,
opErr error) (err error) {
// Make sure we destroy the message, as required by readMessage.
defer c.destroyInMessage(m)
// Clean up state for this op.
c.finishOp(m.Header().Opcode, m.Header().Unique)
// Debug logging
if c.debugLogger != nil {
if opErr == nil {
op.Logf("-> OK: %s", op.DebugString())
} else {
op.Logf("-> error: %v", opErr)
}
}
// Error logging
if opErr != nil && c.errorLogger != nil {
c.errorLogger.Printf("(%s) error: %v", op.ShortDesc(), opErr)
}
// Send the reply to the kernel.
err = c.writeMessage(replyMsg)
if err != nil {
err = fmt.Errorf("writeMessage: %v", err)
return
}
return
}
// Convert the message to an Op.
op, err = fuseops.Convert(
opCtx,
m,
c.protocol,
debugLogForOp,
c.errorLogger,
sendReply)
if err != nil {
err = fmt.Errorf("fuseops.Convert: %v", err)
return
}
// Log the receipt of the operation.
c.debugLog(opID, 1, "<- %v", op.ShortDesc())
// Special case: responding to statfs is required to make mounting work on
// OS X. We don't currently expose the capability for the file system to
// intercept this.
if _, ok := op.(*fuseops.InternalStatFSOp); ok {
op.Respond(nil)
continue
}
// Special case: handle interrupt requests.
if interruptOp, ok := op.(*fuseops.InternalInterruptOp); ok {
c.handleInterrupt(interruptOp.FuseID) c.handleInterrupt(interruptOp.FuseID)
continue continue
} }
// Set up a context that remembers information about this op.
ctx = c.beginOp(m.Header().Opcode, m.Header().Unique)
ctx = context.WithValue(ctx, contextKey, opState{m, op, opID})
// Special case: responding to statfs is required to make mounting work on
// OS X. We don't currently expose the capability for the file system to
// intercept this.
if _, ok := op.(*statFSOp); ok {
c.Reply(ctx, nil)
continue
}
// Return the op to the user.
return return
} }
} }
// Reply to an op previously read using ReadOp, with the supplied error (or nil
// if successful). The context must be the context returned by ReadOp.
//
// LOCKS_EXCLUDED(c.mu)
func (c *Connection) Reply(ctx context.Context, opErr error) {
// Extract the state we stuffed in earlier.
state, ok := ctx.Value(contextKey).(opState)
if !ok {
panic(fmt.Sprintf("Reply called with invalid context: %#v", ctx))
}
op := state.op
m := state.inMsg
opID := state.opID
// Make sure we destroy the message when we're done.
defer c.destroyInMessage(m)
// Clean up state for this op.
c.finishOp(m.Header().Opcode, m.Header().Unique)
// Debug logging
if c.debugLogger != nil {
if opErr == nil {
c.debugLog(opID, 1, "-> OK: %#v", op)
} else {
c.debugLog(opID, 1, "-> error: %v", opErr)
}
}
// Error logging
if opErr != nil && c.errorLogger != nil {
c.errorLogger.Printf("(%#v) error: %v", op, opErr)
}
// Send the reply to the kernel.
replyMsg := kernelResponse(m.Header().Unique, op, opErr, c.protocol)
if replyMsg != nil {
if err := c.writeMessage(replyMsg); err != nil {
if c.errorLogger != nil {
c.errorLogger.Printf("writeMessage: %v", err)
}
return
}
}
}
// Close the connection. Must not be called until operations that were read // Close the connection. Must not be called until operations that were read
// from the connection have been responded to. // from the connection have been responded to.
func (c *Connection) close() (err error) { func (c *Connection) close() (err error) {

View File

@ -12,41 +12,33 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package fuseops package fuse
import ( import (
"bytes" "bytes"
"errors" "errors"
"log" "fmt"
"os" "os"
"syscall" "syscall"
"time" "time"
"unsafe" "unsafe"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/fuse/internal/fusekernel" "github.com/jacobsa/fuse/internal/fusekernel"
"golang.org/x/net/context"
) )
// This function is an implementation detail of the fuse package, and must not ////////////////////////////////////////////////////////////////////////
// be called by anyone else. // Incoming messages
// ////////////////////////////////////////////////////////////////////////
// Convert the supplied fuse kernel message to an Op. sendReply will be used to
// send the reply back to the kernel once the user calls o.Respond. If the op
// is unknown, a special unexported type will be used.
//
// The debug logging function and error logger may be nil. The caller is
// responsible for arranging for the message to be destroyed.
func Convert(
opCtx context.Context,
m *buffer.InMessage,
protocol fusekernel.Protocol,
debugLogForOp func(int, string, ...interface{}),
errorLogger *log.Logger,
sendReply replyFunc) (o Op, err error) {
var co *commonOp
var io internalOp // Convert a kernel message to an appropriate op. If the op is unknown, a
// special unexported type will be used.
//
// The caller is responsible for arranging for the message to be destroyed.
func convertInMessage(
m *buffer.InMessage,
protocol fusekernel.Protocol) (o interface{}, err error) {
switch m.Header().Opcode { switch m.Header().Opcode {
case fusekernel.OpLookup: case fusekernel.OpLookup:
buf := m.ConsumeBytes(m.Len()) buf := m.ConsumeBytes(m.Len())
@ -56,21 +48,15 @@ func Convert(
return return
} }
to := &LookUpInodeOp{ o = &fuseops.LookUpInodeOp{
protocol: protocol, Parent: fuseops.InodeID(m.Header().Nodeid),
Parent: InodeID(m.Header().Nodeid), Name: string(buf[:n-1]),
Name: string(buf[:n-1]),
} }
io = to
co = &to.commonOp
case fusekernel.OpGetattr: case fusekernel.OpGetattr:
to := &GetInodeAttributesOp{ o = &fuseops.GetInodeAttributesOp{
protocol: protocol, Inode: fuseops.InodeID(m.Header().Nodeid),
Inode: InodeID(m.Header().Nodeid),
} }
io = to
co = &to.commonOp
case fusekernel.OpSetattr: case fusekernel.OpSetattr:
type input fusekernel.SetattrIn type input fusekernel.SetattrIn
@ -80,10 +66,10 @@ func Convert(
return return
} }
to := &SetInodeAttributesOp{ to := &fuseops.SetInodeAttributesOp{
protocol: protocol, Inode: fuseops.InodeID(m.Header().Nodeid),
Inode: InodeID(m.Header().Nodeid),
} }
o = to
valid := fusekernel.SetattrValid(in.Valid) valid := fusekernel.SetattrValid(in.Valid)
if valid&fusekernel.SetattrSize != 0 { if valid&fusekernel.SetattrSize != 0 {
@ -105,9 +91,6 @@ func Convert(
to.Mtime = &t to.Mtime = &t
} }
io = to
co = &to.commonOp
case fusekernel.OpForget: case fusekernel.OpForget:
type input fusekernel.ForgetIn type input fusekernel.ForgetIn
in := (*input)(m.Consume(unsafe.Sizeof(input{}))) in := (*input)(m.Consume(unsafe.Sizeof(input{})))
@ -116,12 +99,10 @@ func Convert(
return return
} }
to := &ForgetInodeOp{ o = &fuseops.ForgetInodeOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
N: in.Nlookup, N: in.Nlookup,
} }
io = to
co = &to.commonOp
case fusekernel.OpMkdir: case fusekernel.OpMkdir:
in := (*fusekernel.MkdirIn)(m.Consume(fusekernel.MkdirInSize(protocol))) in := (*fusekernel.MkdirIn)(m.Consume(fusekernel.MkdirInSize(protocol)))
@ -138,10 +119,9 @@ func Convert(
} }
name = name[:i] name = name[:i]
to := &MkDirOp{ o = &fuseops.MkDirOp{
protocol: protocol, Parent: fuseops.InodeID(m.Header().Nodeid),
Parent: InodeID(m.Header().Nodeid), Name: string(name),
Name: string(name),
// On Linux, vfs_mkdir calls through to the inode with at most // On Linux, vfs_mkdir calls through to the inode with at most
// permissions and sticky bits set (cf. https://goo.gl/WxgQXk), and fuse // permissions and sticky bits set (cf. https://goo.gl/WxgQXk), and fuse
@ -152,9 +132,6 @@ func Convert(
Mode: convertFileMode(in.Mode) | os.ModeDir, Mode: convertFileMode(in.Mode) | os.ModeDir,
} }
io = to
co = &to.commonOp
case fusekernel.OpCreate: case fusekernel.OpCreate:
in := (*fusekernel.CreateIn)(m.Consume(fusekernel.CreateInSize(protocol))) in := (*fusekernel.CreateIn)(m.Consume(fusekernel.CreateInSize(protocol)))
if in == nil { if in == nil {
@ -170,14 +147,11 @@ func Convert(
} }
name = name[:i] name = name[:i]
to := &CreateFileOp{ o = &fuseops.CreateFileOp{
protocol: protocol, Parent: fuseops.InodeID(m.Header().Nodeid),
Parent: InodeID(m.Header().Nodeid), Name: string(name),
Name: string(name), Mode: convertFileMode(in.Mode),
Mode: convertFileMode(in.Mode),
} }
io = to
co = &to.commonOp
case fusekernel.OpSymlink: case fusekernel.OpSymlink:
// The message is "newName\0target\0". // The message is "newName\0target\0".
@ -193,14 +167,11 @@ func Convert(
} }
newName, target := names[0:i], names[i+1:len(names)-1] newName, target := names[0:i], names[i+1:len(names)-1]
to := &CreateSymlinkOp{ o = &fuseops.CreateSymlinkOp{
protocol: protocol, Parent: fuseops.InodeID(m.Header().Nodeid),
Parent: InodeID(m.Header().Nodeid), Name: string(newName),
Name: string(newName), Target: string(target),
Target: string(target),
} }
io = to
co = &to.commonOp
case fusekernel.OpRename: case fusekernel.OpRename:
type input fusekernel.RenameIn type input fusekernel.RenameIn
@ -227,14 +198,12 @@ func Convert(
} }
oldName, newName := names[:i], names[i+1:len(names)-1] oldName, newName := names[:i], names[i+1:len(names)-1]
to := &RenameOp{ o = &fuseops.RenameOp{
OldParent: InodeID(m.Header().Nodeid), OldParent: fuseops.InodeID(m.Header().Nodeid),
OldName: string(oldName), OldName: string(oldName),
NewParent: InodeID(in.Newdir), NewParent: fuseops.InodeID(in.Newdir),
NewName: string(newName), NewName: string(newName),
} }
io = to
co = &to.commonOp
case fusekernel.OpUnlink: case fusekernel.OpUnlink:
buf := m.ConsumeBytes(m.Len()) buf := m.ConsumeBytes(m.Len())
@ -244,12 +213,10 @@ func Convert(
return return
} }
to := &UnlinkOp{ o = &fuseops.UnlinkOp{
Parent: InodeID(m.Header().Nodeid), Parent: fuseops.InodeID(m.Header().Nodeid),
Name: string(buf[:n-1]), Name: string(buf[:n-1]),
} }
io = to
co = &to.commonOp
case fusekernel.OpRmdir: case fusekernel.OpRmdir:
buf := m.ConsumeBytes(m.Len()) buf := m.ConsumeBytes(m.Len())
@ -259,26 +226,20 @@ func Convert(
return return
} }
to := &RmDirOp{ o = &fuseops.RmDirOp{
Parent: InodeID(m.Header().Nodeid), Parent: fuseops.InodeID(m.Header().Nodeid),
Name: string(buf[:n-1]), Name: string(buf[:n-1]),
} }
io = to
co = &to.commonOp
case fusekernel.OpOpen: case fusekernel.OpOpen:
to := &OpenFileOp{ o = &fuseops.OpenFileOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
} }
io = to
co = &to.commonOp
case fusekernel.OpOpendir: case fusekernel.OpOpendir:
to := &OpenDirOp{ o = &fuseops.OpenDirOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
} }
io = to
co = &to.commonOp
case fusekernel.OpRead: case fusekernel.OpRead:
in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol))) in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol)))
@ -287,14 +248,12 @@ func Convert(
return return
} }
to := &ReadFileOp{ o = &fuseops.ReadFileOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
Handle: HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
Offset: int64(in.Offset), Offset: int64(in.Offset),
Size: int(in.Size), Size: int(in.Size),
} }
io = to
co = &to.commonOp
case fusekernel.OpReaddir: case fusekernel.OpReaddir:
in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol))) in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol)))
@ -303,14 +262,12 @@ func Convert(
return return
} }
to := &ReadDirOp{ o = &fuseops.ReadDirOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
Handle: HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
Offset: DirOffset(in.Offset), Offset: fuseops.DirOffset(in.Offset),
Size: int(in.Size), Size: int(in.Size),
} }
io = to
co = &to.commonOp
case fusekernel.OpRelease: case fusekernel.OpRelease:
type input fusekernel.ReleaseIn type input fusekernel.ReleaseIn
@ -320,11 +277,9 @@ func Convert(
return return
} }
to := &ReleaseFileHandleOp{ o = &fuseops.ReleaseFileHandleOp{
Handle: HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
} }
io = to
co = &to.commonOp
case fusekernel.OpReleasedir: case fusekernel.OpReleasedir:
type input fusekernel.ReleaseIn type input fusekernel.ReleaseIn
@ -334,11 +289,9 @@ func Convert(
return return
} }
to := &ReleaseDirHandleOp{ o = &fuseops.ReleaseDirHandleOp{
Handle: HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
} }
io = to
co = &to.commonOp
case fusekernel.OpWrite: case fusekernel.OpWrite:
in := (*fusekernel.WriteIn)(m.Consume(fusekernel.WriteInSize(protocol))) in := (*fusekernel.WriteIn)(m.Consume(fusekernel.WriteInSize(protocol)))
@ -353,14 +306,12 @@ func Convert(
return return
} }
to := &WriteFileOp{ o = &fuseops.WriteFileOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
Handle: HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
Data: buf, Data: buf,
Offset: int64(in.Offset), Offset: int64(in.Offset),
} }
io = to
co = &to.commonOp
case fusekernel.OpFsync: case fusekernel.OpFsync:
type input fusekernel.FsyncIn type input fusekernel.FsyncIn
@ -370,12 +321,10 @@ func Convert(
return return
} }
to := &SyncFileOp{ o = &fuseops.SyncFileOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
Handle: HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
} }
io = to
co = &to.commonOp
case fusekernel.OpFlush: case fusekernel.OpFlush:
type input fusekernel.FlushIn type input fusekernel.FlushIn
@ -385,24 +334,18 @@ func Convert(
return return
} }
to := &FlushFileOp{ o = &fuseops.FlushFileOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
Handle: HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
} }
io = to
co = &to.commonOp
case fusekernel.OpReadlink: case fusekernel.OpReadlink:
to := &ReadSymlinkOp{ o = &fuseops.ReadSymlinkOp{
Inode: InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
} }
io = to
co = &to.commonOp
case fusekernel.OpStatfs: case fusekernel.OpStatfs:
to := &InternalStatFSOp{} o = &statFSOp{}
io = to
co = &to.commonOp
case fusekernel.OpInterrupt: case fusekernel.OpInterrupt:
type input fusekernel.InterruptIn type input fusekernel.InterruptIn
@ -412,11 +355,9 @@ func Convert(
return return
} }
to := &InternalInterruptOp{ o = &interruptOp{
FuseID: in.Unique, FuseID: in.Unique,
} }
io = to
co = &to.commonOp
case fusekernel.OpInit: case fusekernel.OpInit:
type input fusekernel.InitIn type input fusekernel.InitIn
@ -426,35 +367,190 @@ func Convert(
return return
} }
to := &InternalInitOp{ o = &initOp{
Kernel: fusekernel.Protocol{in.Major, in.Minor}, Kernel: fusekernel.Protocol{in.Major, in.Minor},
MaxReadahead: in.MaxReadahead, MaxReadahead: in.MaxReadahead,
Flags: fusekernel.InitFlags(in.Flags), Flags: fusekernel.InitFlags(in.Flags),
} }
io = to
co = &to.commonOp
default: default:
to := &unknownOp{ o = &unknownOp{
opCode: m.Header().Opcode, opCode: m.Header().Opcode,
inode: InodeID(m.Header().Nodeid), inode: fuseops.InodeID(m.Header().Nodeid),
} }
io = to
co = &to.commonOp
} }
co.init(
opCtx,
io,
m.Header().Unique,
sendReply,
debugLogForOp,
errorLogger)
o = io
return return
} }
////////////////////////////////////////////////////////////////////////
// Outgoing messages
////////////////////////////////////////////////////////////////////////
// Return the response that should be sent to the kernel. If the op requires no
// response, return a nil response.
func kernelResponse(
fuseID uint64,
op interface{},
opErr error,
protocol fusekernel.Protocol) (msg []byte) {
// If the user replied with an error, create room enough just for the result
// header and fill it in with an error. Otherwise create an appropriate
// response.
var b buffer.OutMessage
if opErr != nil {
b = buffer.NewOutMessage(0)
if errno, ok := opErr.(syscall.Errno); ok {
b.OutHeader().Error = -int32(errno)
} else {
b.OutHeader().Error = -int32(syscall.EIO)
}
} else {
b = kernelResponseForOp(op, protocol)
}
msg = b.Bytes()
// Fill in the rest of the header, if a response is required.
if msg != nil {
h := b.OutHeader()
h.Unique = fuseID
h.Len = uint32(len(msg))
}
return
}
// Like kernelResponse, but assumes the user replied with a nil error to the
// op. Returns a nil response if no response is required.
func kernelResponseForOp(
op interface{},
protocol fusekernel.Protocol) (b buffer.OutMessage) {
// Create the appropriate output message
switch o := op.(type) {
case *fuseops.LookUpInodeOp:
size := fusekernel.EntryOutSize(protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
case *fuseops.GetInodeAttributesOp:
size := fusekernel.AttrOutSize(protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.AttrOut)(b.Grow(size))
out.AttrValid, out.AttrValidNsec = convertExpirationTime(
o.AttributesExpiration)
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
case *fuseops.SetInodeAttributesOp:
size := fusekernel.AttrOutSize(protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.AttrOut)(b.Grow(size))
out.AttrValid, out.AttrValidNsec = convertExpirationTime(
o.AttributesExpiration)
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
case *fuseops.ForgetInodeOp:
// No response.
case *fuseops.MkDirOp:
size := fusekernel.EntryOutSize(protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
case *fuseops.CreateFileOp:
eSize := fusekernel.EntryOutSize(protocol)
b = buffer.NewOutMessage(eSize + unsafe.Sizeof(fusekernel.OpenOut{}))
e := (*fusekernel.EntryOut)(b.Grow(eSize))
convertChildInodeEntry(&o.Entry, e)
oo := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
oo.Fh = uint64(o.Handle)
case *fuseops.CreateSymlinkOp:
size := fusekernel.EntryOutSize(protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
case *fuseops.RenameOp:
b = buffer.NewOutMessage(0)
case *fuseops.RmDirOp:
b = buffer.NewOutMessage(0)
case *fuseops.UnlinkOp:
b = buffer.NewOutMessage(0)
case *fuseops.OpenDirOp:
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
out.Fh = uint64(o.Handle)
case *fuseops.ReadDirOp:
b = buffer.NewOutMessage(uintptr(len(o.Data)))
b.Append(o.Data)
case *fuseops.ReleaseDirHandleOp:
b = buffer.NewOutMessage(0)
case *fuseops.OpenFileOp:
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
out.Fh = uint64(o.Handle)
case *fuseops.ReadFileOp:
b = buffer.NewOutMessage(uintptr(len(o.Data)))
b.Append(o.Data)
case *fuseops.WriteFileOp:
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.WriteOut{}))
out := (*fusekernel.WriteOut)(b.Grow(unsafe.Sizeof(fusekernel.WriteOut{})))
out.Size = uint32(len(o.Data))
case *fuseops.SyncFileOp:
b = buffer.NewOutMessage(0)
case *fuseops.FlushFileOp:
b = buffer.NewOutMessage(0)
case *fuseops.ReleaseFileHandleOp:
b = buffer.NewOutMessage(0)
case *fuseops.ReadSymlinkOp:
b = buffer.NewOutMessage(uintptr(len(o.Target)))
b.AppendString(o.Target)
case *statFSOp:
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.StatfsOut{}))
b.Grow(unsafe.Sizeof(fusekernel.StatfsOut{}))
case *interruptOp:
// No response.
case *initOp:
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.InitOut{}))
out := (*fusekernel.InitOut)(b.Grow(unsafe.Sizeof(fusekernel.InitOut{})))
out.Major = o.Library.Major
out.Minor = o.Library.Minor
out.MaxReadahead = o.MaxReadahead
out.Flags = uint32(o.Flags)
out.MaxWrite = o.MaxWrite
default:
panic(fmt.Sprintf("Unknown op: %#v", op))
}
return
}
////////////////////////////////////////////////////////////////////////
// General conversions
////////////////////////////////////////////////////////////////////////
func convertTime(t time.Time) (secs uint64, nsec uint32) { func convertTime(t time.Time) (secs uint64, nsec uint32) {
totalNano := t.UnixNano() totalNano := t.UnixNano()
secs = uint64(totalNano / 1e9) secs = uint64(totalNano / 1e9)
@ -463,8 +559,8 @@ func convertTime(t time.Time) (secs uint64, nsec uint32) {
} }
func convertAttributes( func convertAttributes(
inodeID InodeID, inodeID fuseops.InodeID,
in *InodeAttributes, in *fuseops.InodeAttributes,
out *fusekernel.Attr) { out *fusekernel.Attr) {
out.Ino = uint64(inodeID) out.Ino = uint64(inodeID)
out.Size = in.Size out.Size = in.Size
@ -515,7 +611,7 @@ func convertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
} }
func convertChildInodeEntry( func convertChildInodeEntry(
in *ChildInodeEntry, in *fuseops.ChildInodeEntry,
out *fusekernel.EntryOut) { out *fusekernel.EntryOut) {
out.Nodeid = uint64(in.Child) out.Nodeid = uint64(in.Child)
out.Generation = uint64(in.Generation) out.Generation = uint64(in.Generation)

View File

@ -18,7 +18,7 @@ import "syscall"
const ( const (
// Errors corresponding to kernel error numbers. These may be treated // Errors corresponding to kernel error numbers. These may be treated
// specially by fuseops.Op.Respond methods. // specially by Connection.Reply.
EEXIST = syscall.EEXIST EEXIST = syscall.EEXIST
EINVAL = syscall.EINVAL EINVAL = syscall.EINVAL
EIO = syscall.EIO EIO = syscall.EIO

View File

@ -1,174 +0,0 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fuseops
import (
"fmt"
"log"
"reflect"
"strings"
"syscall"
"github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/reqtrace"
"golang.org/x/net/context"
)
// An interface that all ops inside which commonOp is embedded must
// implement.
type internalOp interface {
Op
// Create a response message for the kernel, leaving the leading
// fusekernel.OutHeader untouched.
//
// Special case: a zero return value means that the kernel is not expecting a
// response.
kernelResponse() (b buffer.OutMessage)
}
// A function that sends a reply message back to the kernel for the request
// with the given fuse unique ID. The error argument is for informational
// purposes only; the error to hand to the kernel is encoded in the message.
type replyFunc func(Op, uint64, []byte, error) error
// A helper for embedding common behavior.
type commonOp struct {
// The context exposed to the user.
ctx context.Context
// The op in which this struct is embedded.
op internalOp
// The fuse unique ID of this request, as assigned by the kernel.
fuseID uint64
// A function that can be used to send a reply to the kernel.
sendReply replyFunc
// A function that can be used to log debug information about the op. The
// first argument is a call depth.
//
// May be nil.
debugLog func(int, string, ...interface{})
// A logger to be used for logging exceptional errors.
//
// May be nil.
errorLogger *log.Logger
}
func (o *commonOp) ShortDesc() (desc string) {
v := reflect.ValueOf(o.op)
opName := v.Type().String()
// Attempt to better handle the usual case: a string that looks like
// "*fuseops.GetInodeAttributesOp".
const prefix = "*fuseops."
const suffix = "Op"
if strings.HasPrefix(opName, prefix) && strings.HasSuffix(opName, suffix) {
opName = opName[len(prefix) : len(opName)-len(suffix)]
}
desc = opName
// Include the inode number to which the op applies, if possible.
if f := v.Elem().FieldByName("Inode"); f.IsValid() {
desc = fmt.Sprintf("%s(inode=%v)", desc, f.Interface())
}
return
}
func (o *commonOp) DebugString() string {
// By default, defer to ShortDesc.
return o.op.ShortDesc()
}
func (o *commonOp) init(
ctx context.Context,
op internalOp,
fuseID uint64,
sendReply replyFunc,
debugLog func(int, string, ...interface{}),
errorLogger *log.Logger) {
// Initialize basic fields.
o.ctx = ctx
o.op = op
o.fuseID = fuseID
o.sendReply = sendReply
o.debugLog = debugLog
o.errorLogger = errorLogger
// Set up a trace span for this op.
var reportForTrace reqtrace.ReportFunc
o.ctx, reportForTrace = reqtrace.StartSpan(o.ctx, o.op.ShortDesc())
// When the op is finished, report to both reqtrace and the connection.
prevSendReply := o.sendReply
o.sendReply = func(op Op, fuseID uint64, msg []byte, opErr error) (err error) {
reportForTrace(opErr)
err = prevSendReply(op, fuseID, msg, opErr)
return
}
}
func (o *commonOp) Context() context.Context {
return o.ctx
}
func (o *commonOp) Logf(format string, v ...interface{}) {
if o.debugLog == nil {
return
}
const calldepth = 2
o.debugLog(calldepth, format, v...)
}
func (o *commonOp) Respond(err error) {
// If successful, we ask the op for an appopriate response to the kernel, and
// it is responsible for leaving room for the fusekernel.OutHeader struct.
// Otherwise, create our own.
var b buffer.OutMessage
if err == nil {
b = o.op.kernelResponse()
} else {
b = buffer.NewOutMessage(0)
}
// Fill in the header if a reply is needed.
msg := b.Bytes()
if msg != nil {
h := b.OutHeader()
h.Unique = o.fuseID
h.Len = uint32(len(msg))
if err != nil {
// If the user gave us a syscall.Errno, use that value in the reply.
// Otherwise use the generic EIO.
if errno, ok := err.(syscall.Errno); ok {
h.Error = -int32(errno)
} else {
h.Error = -int32(syscall.EIO)
}
}
}
// Reply.
replyErr := o.sendReply(o.op, o.fuseID, msg, err)
if replyErr != nil && o.errorLogger != nil {
o.errorLogger.Printf("Error from sendReply: %v", replyErr)
}
}

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package fuseops contains implementations of the fuse.Op interface that may // Package fuseops contains ops that may be returned by fuse.Connection.ReadOp.
// be returned by fuse.Connection.ReadOp. See documentation in that package for // See documentation in that package for more.
// more.
package fuseops package fuseops

View File

@ -15,43 +15,10 @@
package fuseops package fuseops
import ( import (
"fmt"
"os" "os"
"time" "time"
"unsafe"
"github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/fuse/internal/fusekernel"
"golang.org/x/net/context"
) )
// A common interface implemented by all ops in this package. Use a type switch
// to find particular concrete types, responding with fuse.ENOSYS if a type is
// not supported.
type Op interface {
// A short description of the op, to be used in logging.
ShortDesc() string
// A long description of the op, to be used in debug logging.
DebugString() string
// A context that can be used for long-running operations.
Context() context.Context
// Repond to the operation with the supplied error. If there is no error, set
// any necessary output fields and then call Respond(nil). The user must not
// call with a nil error for unrecognized ops; instead, use ENOSYS.
//
// Once this is invoked, the user must exclude any further calls to any
// method of this op.
Respond(error)
// Log information tied to this operation, with semantics equivalent to
// log.Printf, except that the format is different and logging is suppressed
// if no debug logger was set when mounting.
Logf(format string, v ...interface{})
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Inodes // Inodes
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -59,9 +26,6 @@ type Op interface {
// Look up a child by name within a parent directory. The kernel sends this // Look up a child by name within a parent directory. The kernel sends this
// when resolving user paths to dentry structs, which are then cached. // when resolving user paths to dentry structs, which are then cached.
type LookUpInodeOp struct { type LookUpInodeOp struct {
commonOp
protocol fusekernel.Protocol
// The ID of the directory inode to which the child belongs. // The ID of the directory inode to which the child belongs.
Parent InodeID Parent InodeID
@ -83,28 +47,11 @@ type LookUpInodeOp struct {
Entry ChildInodeEntry Entry ChildInodeEntry
} }
func (o *LookUpInodeOp) ShortDesc() (desc string) {
desc = fmt.Sprintf("LookUpInode(parent=%v, name=%q)", o.Parent, o.Name)
return
}
func (o *LookUpInodeOp) kernelResponse() (b buffer.OutMessage) {
size := fusekernel.EntryOutSize(o.protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
return
}
// Refresh the attributes for an inode whose ID was previously returned in a // Refresh the attributes for an inode whose ID was previously returned in a
// LookUpInodeOp. The kernel sends this when the FUSE VFS layer's cache of // LookUpInodeOp. The kernel sends this when the FUSE VFS layer's cache of
// inode attributes is stale. This is controlled by the AttributesExpiration // inode attributes is stale. This is controlled by the AttributesExpiration
// field of ChildInodeEntry, etc. // field of ChildInodeEntry, etc.
type GetInodeAttributesOp struct { type GetInodeAttributesOp struct {
commonOp
protocol fusekernel.Protocol
// The inode of interest. // The inode of interest.
Inode InodeID Inode InodeID
@ -115,32 +62,11 @@ type GetInodeAttributesOp struct {
AttributesExpiration time.Time AttributesExpiration time.Time
} }
func (o *GetInodeAttributesOp) DebugString() string {
return fmt.Sprintf(
"Inode: %d, Exp: %v, Attr: %s",
o.Inode,
o.AttributesExpiration,
o.Attributes.DebugString())
}
func (o *GetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) {
size := fusekernel.AttrOutSize(o.protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.AttrOut)(b.Grow(size))
out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration)
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
return
}
// Change attributes for an inode. // Change attributes for an inode.
// //
// The kernel sends this for obvious cases like chmod(2), and for less obvious // The kernel sends this for obvious cases like chmod(2), and for less obvious
// cases like ftrunctate(2). // cases like ftrunctate(2).
type SetInodeAttributesOp struct { type SetInodeAttributesOp struct {
commonOp
protocol fusekernel.Protocol
// The inode of interest. // The inode of interest.
Inode InodeID Inode InodeID
@ -157,16 +83,6 @@ type SetInodeAttributesOp struct {
AttributesExpiration time.Time AttributesExpiration time.Time
} }
func (o *SetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) {
size := fusekernel.AttrOutSize(o.protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.AttrOut)(b.Grow(size))
out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration)
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
return
}
// Decrement the reference count for an inode ID previously issued by the file // Decrement the reference count for an inode ID previously issued by the file
// system. // system.
// //
@ -207,8 +123,6 @@ func (o *SetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) {
// Rather they should take fuse.Connection.ReadOp returning io.EOF as // Rather they should take fuse.Connection.ReadOp returning io.EOF as
// implicitly decrementing all lookup counts to zero. // implicitly decrementing all lookup counts to zero.
type ForgetInodeOp struct { type ForgetInodeOp struct {
commonOp
// The inode whose reference count should be decremented. // The inode whose reference count should be decremented.
Inode InodeID Inode InodeID
@ -216,11 +130,6 @@ type ForgetInodeOp struct {
N uint64 N uint64
} }
func (o *ForgetInodeOp) kernelResponse() (b buffer.OutMessage) {
// No response.
return
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Inode creation // Inode creation
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -237,9 +146,6 @@ func (o *ForgetInodeOp) kernelResponse() (b buffer.OutMessage) {
// //
// Therefore the file system should return EEXIST if the name already exists. // Therefore the file system should return EEXIST if the name already exists.
type MkDirOp struct { type MkDirOp struct {
commonOp
protocol fusekernel.Protocol
// The ID of parent directory inode within which to create the child. // The ID of parent directory inode within which to create the child.
Parent InodeID Parent InodeID
@ -254,20 +160,6 @@ type MkDirOp struct {
Entry ChildInodeEntry Entry ChildInodeEntry
} }
func (o *MkDirOp) ShortDesc() (desc string) {
desc = fmt.Sprintf("MkDir(parent=%v, name=%q)", o.Parent, o.Name)
return
}
func (o *MkDirOp) kernelResponse() (b buffer.OutMessage) {
size := fusekernel.EntryOutSize(o.protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
return
}
// Create a file inode and open it. // Create a file inode and open it.
// //
// The kernel sends this when the user asks to open a file with the O_CREAT // The kernel sends this when the user asks to open a file with the O_CREAT
@ -279,9 +171,6 @@ func (o *MkDirOp) kernelResponse() (b buffer.OutMessage) {
// //
// Therefore the file system should return EEXIST if the name already exists. // Therefore the file system should return EEXIST if the name already exists.
type CreateFileOp struct { type CreateFileOp struct {
commonOp
protocol fusekernel.Protocol
// The ID of parent directory inode within which to create the child file. // The ID of parent directory inode within which to create the child file.
Parent InodeID Parent InodeID
@ -306,30 +195,9 @@ type CreateFileOp struct {
Handle HandleID Handle HandleID
} }
func (o *CreateFileOp) ShortDesc() (desc string) {
desc = fmt.Sprintf("CreateFile(parent=%v, name=%q)", o.Parent, o.Name)
return
}
func (o *CreateFileOp) kernelResponse() (b buffer.OutMessage) {
eSize := fusekernel.EntryOutSize(o.protocol)
b = buffer.NewOutMessage(eSize + unsafe.Sizeof(fusekernel.OpenOut{}))
e := (*fusekernel.EntryOut)(b.Grow(eSize))
convertChildInodeEntry(&o.Entry, e)
oo := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
oo.Fh = uint64(o.Handle)
return
}
// Create a symlink inode. If the name already exists, the file system should // Create a symlink inode. If the name already exists, the file system should
// return EEXIST (cf. the notes on CreateFileOp and MkDirOp). // return EEXIST (cf. the notes on CreateFileOp and MkDirOp).
type CreateSymlinkOp struct { type CreateSymlinkOp struct {
commonOp
protocol fusekernel.Protocol
// The ID of parent directory inode within which to create the child symlink. // The ID of parent directory inode within which to create the child symlink.
Parent InodeID Parent InodeID
@ -347,25 +215,6 @@ type CreateSymlinkOp struct {
Entry ChildInodeEntry Entry ChildInodeEntry
} }
func (o *CreateSymlinkOp) ShortDesc() (desc string) {
desc = fmt.Sprintf(
"CreateSymlink(parent=%v, name=%q, target=%q)",
o.Parent,
o.Name,
o.Target)
return
}
func (o *CreateSymlinkOp) kernelResponse() (b buffer.OutMessage) {
size := fusekernel.EntryOutSize(o.protocol)
b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
return
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Unlinking // Unlinking
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -405,8 +254,6 @@ func (o *CreateSymlinkOp) kernelResponse() (b buffer.OutMessage) {
// about this. // about this.
// //
type RenameOp struct { type RenameOp struct {
commonOp
// The old parent directory, and the name of the entry within it to be // The old parent directory, and the name of the entry within it to be
// relocated. // relocated.
OldParent InodeID OldParent InodeID
@ -418,11 +265,6 @@ type RenameOp struct {
NewName string NewName string
} }
func (o *RenameOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(0)
return
}
// Unlink a directory from its parent. Because directories cannot have a link // Unlink a directory from its parent. Because directories cannot have a link
// count above one, this means the directory inode should be deleted as well // count above one, this means the directory inode should be deleted as well
// once the kernel sends ForgetInodeOp. // once the kernel sends ForgetInodeOp.
@ -431,19 +273,12 @@ func (o *RenameOp) kernelResponse() (b buffer.OutMessage) {
// //
// Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf) // Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf)
type RmDirOp struct { type RmDirOp struct {
commonOp
// The ID of parent directory inode, and the name of the directory being // The ID of parent directory inode, and the name of the directory being
// removed within it. // removed within it.
Parent InodeID Parent InodeID
Name string Name string
} }
func (o *RmDirOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(0)
return
}
// Unlink a file or symlink from its parent. If this brings the inode's link // Unlink a file or symlink from its parent. If this brings the inode's link
// count to zero, the inode should be deleted once the kernel sends // count to zero, the inode should be deleted once the kernel sends
// ForgetInodeOp. It may still be referenced before then if a user still has // ForgetInodeOp. It may still be referenced before then if a user still has
@ -451,19 +286,12 @@ func (o *RmDirOp) kernelResponse() (b buffer.OutMessage) {
// //
// Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C) // Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C)
type UnlinkOp struct { type UnlinkOp struct {
commonOp
// The ID of parent directory inode, and the name of the entry being removed // The ID of parent directory inode, and the name of the entry being removed
// within it. // within it.
Parent InodeID Parent InodeID
Name string Name string
} }
func (o *UnlinkOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(0)
return
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Directory handles // Directory handles
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -475,8 +303,6 @@ func (o *UnlinkOp) kernelResponse() (b buffer.OutMessage) {
// user-space process. On OS X it may not be sent for every open(2) (cf. // user-space process. On OS X it may not be sent for every open(2) (cf.
// https://github.com/osxfuse/osxfuse/issues/199). // https://github.com/osxfuse/osxfuse/issues/199).
type OpenDirOp struct { type OpenDirOp struct {
commonOp
// The ID of the inode to be opened. // The ID of the inode to be opened.
Inode InodeID Inode InodeID
@ -491,18 +317,8 @@ type OpenDirOp struct {
Handle HandleID Handle HandleID
} }
func (o *OpenDirOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
out.Fh = uint64(o.Handle)
return
}
// Read entries from a directory previously opened with OpenDir. // Read entries from a directory previously opened with OpenDir.
type ReadDirOp struct { type ReadDirOp struct {
commonOp
// The directory inode that we are reading, and the handle previously // The directory inode that we are reading, and the handle previously
// returned by OpenDir when opening that inode. // returned by OpenDir when opening that inode.
Inode InodeID Inode InodeID
@ -589,12 +405,6 @@ type ReadDirOp struct {
Data []byte Data []byte
} }
func (o *ReadDirOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(uintptr(len(o.Data)))
b.Append(o.Data)
return
}
// Release a previously-minted directory handle. The kernel sends this when // Release a previously-minted directory handle. The kernel sends this when
// there are no more references to an open directory: all file descriptors are // there are no more references to an open directory: all file descriptors are
// closed and all memory mappings are unmapped. // closed and all memory mappings are unmapped.
@ -604,19 +414,12 @@ func (o *ReadDirOp) kernelResponse() (b buffer.OutMessage) {
// //
// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do). // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
type ReleaseDirHandleOp struct { type ReleaseDirHandleOp struct {
commonOp
// The handle ID to be released. The kernel guarantees that this ID will not // The handle ID to be released. The kernel guarantees that this ID will not
// be used in further calls to the file system (unless it is reissued by the // be used in further calls to the file system (unless it is reissued by the
// file system). // file system).
Handle HandleID Handle HandleID
} }
func (o *ReleaseDirHandleOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(0)
return
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// File handles // File handles
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -628,8 +431,6 @@ func (o *ReleaseDirHandleOp) kernelResponse() (b buffer.OutMessage) {
// process. On OS X it may not be sent for every open(2) // process. On OS X it may not be sent for every open(2)
// (cf.https://github.com/osxfuse/osxfuse/issues/199). // (cf.https://github.com/osxfuse/osxfuse/issues/199).
type OpenFileOp struct { type OpenFileOp struct {
commonOp
// The ID of the inode to be opened. // The ID of the inode to be opened.
Inode InodeID Inode InodeID
@ -643,22 +444,12 @@ type OpenFileOp struct {
Handle HandleID Handle HandleID
} }
func (o *OpenFileOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
out.Fh = uint64(o.Handle)
return
}
// Read data from a file previously opened with CreateFile or OpenFile. // Read data from a file previously opened with CreateFile or OpenFile.
// //
// Note that this op is not sent for every call to read(2) by the end user; // Note that this op is not sent for every call to read(2) by the end user;
// some reads may be served by the page cache. See notes on WriteFileOp for // some reads may be served by the page cache. See notes on WriteFileOp for
// more. // more.
type ReadFileOp struct { type ReadFileOp struct {
commonOp
// The file inode that we are reading, and the handle previously returned by // The file inode that we are reading, and the handle previously returned by
// CreateFile or OpenFile when opening that inode. // CreateFile or OpenFile when opening that inode.
Inode InodeID Inode InodeID
@ -680,12 +471,6 @@ type ReadFileOp struct {
Data []byte Data []byte
} }
func (o *ReadFileOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(uintptr(len(o.Data)))
b.Append(o.Data)
return
}
// Write data to a file previously opened with CreateFile or OpenFile. // Write data to a file previously opened with CreateFile or OpenFile.
// //
// When the user writes data using write(2), the write goes into the page // When the user writes data using write(2), the write goes into the page
@ -718,8 +503,6 @@ func (o *ReadFileOp) kernelResponse() (b buffer.OutMessage) {
// (See also http://goo.gl/ocdTdM, fuse-devel thread "Fuse guarantees on // (See also http://goo.gl/ocdTdM, fuse-devel thread "Fuse guarantees on
// concurrent requests".) // concurrent requests".)
type WriteFileOp struct { type WriteFileOp struct {
commonOp
// The file inode that we are modifying, and the handle previously returned // The file inode that we are modifying, and the handle previously returned
// by CreateFile or OpenFile when opening that inode. // by CreateFile or OpenFile when opening that inode.
Inode InodeID Inode InodeID
@ -756,14 +539,6 @@ type WriteFileOp struct {
Data []byte Data []byte
} }
func (o *WriteFileOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.WriteOut{}))
out := (*fusekernel.WriteOut)(b.Grow(unsafe.Sizeof(fusekernel.WriteOut{})))
out.Size = uint32(len(o.Data))
return
}
// Synchronize the current contents of an open file to storage. // Synchronize the current contents of an open file to storage.
// //
// vfs.txt documents this as being called for by the fsync(2) system call // vfs.txt documents this as being called for by the fsync(2) system call
@ -781,18 +556,11 @@ func (o *WriteFileOp) kernelResponse() (b buffer.OutMessage) {
// See also: FlushFileOp, which may perform a similar function when closing a // See also: FlushFileOp, which may perform a similar function when closing a
// file (but which is not used in "real" file systems). // file (but which is not used in "real" file systems).
type SyncFileOp struct { type SyncFileOp struct {
commonOp
// The file and handle being sync'd. // The file and handle being sync'd.
Inode InodeID Inode InodeID
Handle HandleID Handle HandleID
} }
func (o *SyncFileOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(0)
return
}
// Flush the current state of an open file to storage upon closing a file // Flush the current state of an open file to storage upon closing a file
// descriptor. // descriptor.
// //
@ -841,18 +609,11 @@ func (o *SyncFileOp) kernelResponse() (b buffer.OutMessage) {
// to at least schedule a real flush, and maybe do it immediately in order to // to at least schedule a real flush, and maybe do it immediately in order to
// return any errors that occur. // return any errors that occur.
type FlushFileOp struct { type FlushFileOp struct {
commonOp
// The file and handle being flushed. // The file and handle being flushed.
Inode InodeID Inode InodeID
Handle HandleID Handle HandleID
} }
func (o *FlushFileOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(0)
return
}
// Release a previously-minted file handle. The kernel calls this when there // Release a previously-minted file handle. The kernel calls this when there
// are no more references to an open file: all file descriptors are closed // are no more references to an open file: all file descriptors are closed
// and all memory mappings are unmapped. // and all memory mappings are unmapped.
@ -862,113 +623,21 @@ func (o *FlushFileOp) kernelResponse() (b buffer.OutMessage) {
// //
// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do). // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
type ReleaseFileHandleOp struct { type ReleaseFileHandleOp struct {
commonOp
// The handle ID to be released. The kernel guarantees that this ID will not // The handle ID to be released. The kernel guarantees that this ID will not
// be used in further calls to the file system (unless it is reissued by the // be used in further calls to the file system (unless it is reissued by the
// file system). // file system).
Handle HandleID Handle HandleID
} }
func (o *ReleaseFileHandleOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(0)
return
}
// A sentinel used for unknown ops. The user is expected to respond with a
// non-nil error.
type unknownOp struct {
commonOp
opCode uint32
inode InodeID
}
func (o *unknownOp) ShortDesc() (desc string) {
desc = fmt.Sprintf("<opcode %d>(inode=%v)", o.opCode, o.inode)
return
}
func (o *unknownOp) kernelResponse() (b buffer.OutMessage) {
panic(fmt.Sprintf("Should never get here for unknown op: %s", o.ShortDesc()))
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Reading symlinks // Reading symlinks
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Read the target of a symlink inode. // Read the target of a symlink inode.
type ReadSymlinkOp struct { type ReadSymlinkOp struct {
commonOp
// The symlink inode that we are reading. // The symlink inode that we are reading.
Inode InodeID Inode InodeID
// Set by the file system: the target of the symlink. // Set by the file system: the target of the symlink.
Target string Target string
} }
func (o *ReadSymlinkOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(uintptr(len(o.Target)))
b.AppendString(o.Target)
return
}
////////////////////////////////////////////////////////////////////////
// Internal
////////////////////////////////////////////////////////////////////////
// TODO(jacobsa): Untangle the way ops work and move these to an internal
// package, along with Convert. I think all of the behavior wants to be on
// Connection. Ops have only String methods. Connection.ReadOp returns an
// interace{} and a context. If we must restore debug logging later, we can
// stuff an op ID in that context and add a Connection.Logf method. Connection
// has a Reply method that takes a descendent context and an error.
// Do not use this struct directly. See the TODO in fuseops/ops.go.
type InternalStatFSOp struct {
commonOp
}
func (o *InternalStatFSOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.StatfsOut{}))
b.Grow(unsafe.Sizeof(fusekernel.StatfsOut{}))
return
}
// Do not use this struct directly. See the TODO in fuseops/ops.go.
type InternalInterruptOp struct {
commonOp
FuseID uint64
}
func (o *InternalInterruptOp) kernelResponse() (b buffer.OutMessage) {
panic("Shouldn't get here.")
}
// Do not use this struct directly. See the TODO in fuseops/ops.go.
type InternalInitOp struct {
commonOp
// In
Kernel fusekernel.Protocol
// Out
Library fusekernel.Protocol
MaxReadahead uint32
Flags fusekernel.InitFlags
MaxWrite uint32
}
func (o *InternalInitOp) kernelResponse() (b buffer.OutMessage) {
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.InitOut{}))
out := (*fusekernel.InitOut)(b.Grow(unsafe.Sizeof(fusekernel.InitOut{})))
out.Major = o.Library.Major
out.Minor = o.Library.Minor
out.MaxReadahead = o.MaxReadahead
out.Flags = uint32(o.Flags)
out.MaxWrite = o.MaxWrite
return
}

View File

@ -18,6 +18,8 @@ import (
"io" "io"
"sync" "sync"
"golang.org/x/net/context"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
) )
@ -27,32 +29,32 @@ import (
// loop" that switches on op types, instead receiving typed method calls // loop" that switches on op types, instead receiving typed method calls
// directly. // directly.
// //
// The FileSystem implementation should not call Op.Respond, instead returning // The FileSystem implementation should not call Connection.Reply, instead
// the error with which the caller should respond. // returning the error with which the caller should respond.
// //
// See NotImplementedFileSystem for a convenient way to embed default // See NotImplementedFileSystem for a convenient way to embed default
// implementations for methods you don't care about. // implementations for methods you don't care about.
type FileSystem interface { type FileSystem interface {
LookUpInode(*fuseops.LookUpInodeOp) error LookUpInode(context.Context, *fuseops.LookUpInodeOp) error
GetInodeAttributes(*fuseops.GetInodeAttributesOp) error GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error
SetInodeAttributes(*fuseops.SetInodeAttributesOp) error SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
ForgetInode(*fuseops.ForgetInodeOp) error ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
MkDir(*fuseops.MkDirOp) error MkDir(context.Context, *fuseops.MkDirOp) error
CreateFile(*fuseops.CreateFileOp) error CreateFile(context.Context, *fuseops.CreateFileOp) error
CreateSymlink(*fuseops.CreateSymlinkOp) error CreateSymlink(context.Context, *fuseops.CreateSymlinkOp) error
Rename(*fuseops.RenameOp) error Rename(context.Context, *fuseops.RenameOp) error
RmDir(*fuseops.RmDirOp) error RmDir(context.Context, *fuseops.RmDirOp) error
Unlink(*fuseops.UnlinkOp) error Unlink(context.Context, *fuseops.UnlinkOp) error
OpenDir(*fuseops.OpenDirOp) error OpenDir(context.Context, *fuseops.OpenDirOp) error
ReadDir(*fuseops.ReadDirOp) error ReadDir(context.Context, *fuseops.ReadDirOp) error
ReleaseDirHandle(*fuseops.ReleaseDirHandleOp) error ReleaseDirHandle(context.Context, *fuseops.ReleaseDirHandleOp) error
OpenFile(*fuseops.OpenFileOp) error OpenFile(context.Context, *fuseops.OpenFileOp) error
ReadFile(*fuseops.ReadFileOp) error ReadFile(context.Context, *fuseops.ReadFileOp) error
WriteFile(*fuseops.WriteFileOp) error WriteFile(context.Context, *fuseops.WriteFileOp) error
SyncFile(*fuseops.SyncFileOp) error SyncFile(context.Context, *fuseops.SyncFileOp) error
FlushFile(*fuseops.FlushFileOp) error FlushFile(context.Context, *fuseops.FlushFileOp) error
ReleaseFileHandle(*fuseops.ReleaseFileHandleOp) error ReleaseFileHandle(context.Context, *fuseops.ReleaseFileHandleOp) error
ReadSymlink(*fuseops.ReadSymlinkOp) error ReadSymlink(context.Context, *fuseops.ReadSymlinkOp) error
// Regard all inodes (including the root inode) as having their lookup counts // Regard all inodes (including the root inode) as having their lookup counts
// decremented to zero, and clean up any resources associated with the file // decremented to zero, and clean up any resources associated with the file
@ -91,7 +93,7 @@ func (s *fileSystemServer) ServeOps(c *fuse.Connection) {
}() }()
for { for {
op, err := c.ReadOp() ctx, op, err := c.ReadOp()
if err == io.EOF { if err == io.EOF {
break break
} }
@ -101,11 +103,14 @@ func (s *fileSystemServer) ServeOps(c *fuse.Connection) {
} }
s.opsInFlight.Add(1) s.opsInFlight.Add(1)
go s.handleOp(op) go s.handleOp(c, ctx, op)
} }
} }
func (s *fileSystemServer) handleOp(op fuseops.Op) { func (s *fileSystemServer) handleOp(
c *fuse.Connection,
ctx context.Context,
op interface{}) {
defer s.opsInFlight.Done() defer s.opsInFlight.Done()
// Dispatch to the appropriate method. // Dispatch to the appropriate method.
@ -115,65 +120,65 @@ func (s *fileSystemServer) handleOp(op fuseops.Op) {
err = fuse.ENOSYS err = fuse.ENOSYS
case *fuseops.LookUpInodeOp: case *fuseops.LookUpInodeOp:
err = s.fs.LookUpInode(typed) err = s.fs.LookUpInode(ctx, typed)
case *fuseops.GetInodeAttributesOp: case *fuseops.GetInodeAttributesOp:
err = s.fs.GetInodeAttributes(typed) err = s.fs.GetInodeAttributes(ctx, typed)
case *fuseops.SetInodeAttributesOp: case *fuseops.SetInodeAttributesOp:
err = s.fs.SetInodeAttributes(typed) err = s.fs.SetInodeAttributes(ctx, typed)
case *fuseops.ForgetInodeOp: case *fuseops.ForgetInodeOp:
err = s.fs.ForgetInode(typed) err = s.fs.ForgetInode(ctx, typed)
case *fuseops.MkDirOp: case *fuseops.MkDirOp:
err = s.fs.MkDir(typed) err = s.fs.MkDir(ctx, typed)
case *fuseops.CreateFileOp: case *fuseops.CreateFileOp:
err = s.fs.CreateFile(typed) err = s.fs.CreateFile(ctx, typed)
case *fuseops.CreateSymlinkOp: case *fuseops.CreateSymlinkOp:
err = s.fs.CreateSymlink(typed) err = s.fs.CreateSymlink(ctx, typed)
case *fuseops.RenameOp: case *fuseops.RenameOp:
err = s.fs.Rename(typed) err = s.fs.Rename(ctx, typed)
case *fuseops.RmDirOp: case *fuseops.RmDirOp:
err = s.fs.RmDir(typed) err = s.fs.RmDir(ctx, typed)
case *fuseops.UnlinkOp: case *fuseops.UnlinkOp:
err = s.fs.Unlink(typed) err = s.fs.Unlink(ctx, typed)
case *fuseops.OpenDirOp: case *fuseops.OpenDirOp:
err = s.fs.OpenDir(typed) err = s.fs.OpenDir(ctx, typed)
case *fuseops.ReadDirOp: case *fuseops.ReadDirOp:
err = s.fs.ReadDir(typed) err = s.fs.ReadDir(ctx, typed)
case *fuseops.ReleaseDirHandleOp: case *fuseops.ReleaseDirHandleOp:
err = s.fs.ReleaseDirHandle(typed) err = s.fs.ReleaseDirHandle(ctx, typed)
case *fuseops.OpenFileOp: case *fuseops.OpenFileOp:
err = s.fs.OpenFile(typed) err = s.fs.OpenFile(ctx, typed)
case *fuseops.ReadFileOp: case *fuseops.ReadFileOp:
err = s.fs.ReadFile(typed) err = s.fs.ReadFile(ctx, typed)
case *fuseops.WriteFileOp: case *fuseops.WriteFileOp:
err = s.fs.WriteFile(typed) err = s.fs.WriteFile(ctx, typed)
case *fuseops.SyncFileOp: case *fuseops.SyncFileOp:
err = s.fs.SyncFile(typed) err = s.fs.SyncFile(ctx, typed)
case *fuseops.FlushFileOp: case *fuseops.FlushFileOp:
err = s.fs.FlushFile(typed) err = s.fs.FlushFile(ctx, typed)
case *fuseops.ReleaseFileHandleOp: case *fuseops.ReleaseFileHandleOp:
err = s.fs.ReleaseFileHandle(typed) err = s.fs.ReleaseFileHandle(ctx, typed)
case *fuseops.ReadSymlinkOp: case *fuseops.ReadSymlinkOp:
err = s.fs.ReadSymlink(typed) err = s.fs.ReadSymlink(ctx, typed)
} }
op.Respond(err) c.Reply(ctx, err)
} }

View File

@ -17,6 +17,7 @@ package fuseutil
import ( import (
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"golang.org/x/net/context"
) )
// A FileSystem that responds to all ops with fuse.ENOSYS. Embed this in your // A FileSystem that responds to all ops with fuse.ENOSYS. Embed this in your
@ -29,120 +30,140 @@ type NotImplementedFileSystem struct {
var _ FileSystem = &NotImplementedFileSystem{} var _ FileSystem = &NotImplementedFileSystem{}
func (fs *NotImplementedFileSystem) LookUpInode( func (fs *NotImplementedFileSystem) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) { op *fuseops.LookUpInodeOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) GetInodeAttributes( func (fs *NotImplementedFileSystem) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) (err error) { op *fuseops.GetInodeAttributesOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) SetInodeAttributes( func (fs *NotImplementedFileSystem) SetInodeAttributes(
ctx context.Context,
op *fuseops.SetInodeAttributesOp) (err error) { op *fuseops.SetInodeAttributesOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) ForgetInode( func (fs *NotImplementedFileSystem) ForgetInode(
ctx context.Context,
op *fuseops.ForgetInodeOp) (err error) { op *fuseops.ForgetInodeOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) MkDir( func (fs *NotImplementedFileSystem) MkDir(
ctx context.Context,
op *fuseops.MkDirOp) (err error) { op *fuseops.MkDirOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) CreateFile( func (fs *NotImplementedFileSystem) CreateFile(
ctx context.Context,
op *fuseops.CreateFileOp) (err error) { op *fuseops.CreateFileOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) CreateSymlink( func (fs *NotImplementedFileSystem) CreateSymlink(
ctx context.Context,
op *fuseops.CreateSymlinkOp) (err error) { op *fuseops.CreateSymlinkOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) Rename( func (fs *NotImplementedFileSystem) Rename(
ctx context.Context,
op *fuseops.RenameOp) (err error) { op *fuseops.RenameOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) RmDir( func (fs *NotImplementedFileSystem) RmDir(
ctx context.Context,
op *fuseops.RmDirOp) (err error) { op *fuseops.RmDirOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) Unlink( func (fs *NotImplementedFileSystem) Unlink(
ctx context.Context,
op *fuseops.UnlinkOp) (err error) { op *fuseops.UnlinkOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) OpenDir( func (fs *NotImplementedFileSystem) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) (err error) { op *fuseops.OpenDirOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) ReadDir( func (fs *NotImplementedFileSystem) ReadDir(
ctx context.Context,
op *fuseops.ReadDirOp) (err error) { op *fuseops.ReadDirOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) ReleaseDirHandle( func (fs *NotImplementedFileSystem) ReleaseDirHandle(
ctx context.Context,
op *fuseops.ReleaseDirHandleOp) (err error) { op *fuseops.ReleaseDirHandleOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) OpenFile( func (fs *NotImplementedFileSystem) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) (err error) { op *fuseops.OpenFileOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) ReadFile( func (fs *NotImplementedFileSystem) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) (err error) { op *fuseops.ReadFileOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) WriteFile( func (fs *NotImplementedFileSystem) WriteFile(
ctx context.Context,
op *fuseops.WriteFileOp) (err error) { op *fuseops.WriteFileOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) SyncFile( func (fs *NotImplementedFileSystem) SyncFile(
ctx context.Context,
op *fuseops.SyncFileOp) (err error) { op *fuseops.SyncFileOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) FlushFile( func (fs *NotImplementedFileSystem) FlushFile(
ctx context.Context,
op *fuseops.FlushFileOp) (err error) { op *fuseops.FlushFileOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) ReleaseFileHandle( func (fs *NotImplementedFileSystem) ReleaseFileHandle(
ctx context.Context,
op *fuseops.ReleaseFileHandleOp) (err error) { op *fuseops.ReleaseFileHandleOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return
} }
func (fs *NotImplementedFileSystem) ReadSymlink( func (fs *NotImplementedFileSystem) ReadSymlink(
ctx context.Context,
op *fuseops.ReadSymlinkOp) (err error) { op *fuseops.ReadSymlinkOp) (err error) {
err = fuse.ENOSYS err = fuse.ENOSYS
return return

48
ops.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fuse
import (
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/internal/fusekernel"
)
// A sentinel used for unknown ops. The user is expected to respond with a
// non-nil error.
type unknownOp struct {
opCode uint32
inode fuseops.InodeID
}
// Required in order to mount on OS X.
type statFSOp struct {
}
// Causes us to cancel the associated context.
type interruptOp struct {
FuseID uint64
}
// Required in order to mount on Linux and OS X.
type initOp struct {
// In
Kernel fusekernel.Protocol
// Out
Library fusekernel.Protocol
MaxReadahead uint32
Flags fusekernel.InitFlags
MaxWrite uint32
}

View File

@ -19,6 +19,8 @@ import (
"os" "os"
"time" "time"
"golang.org/x/net/context"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/fuse/fuseutil"
@ -240,6 +242,7 @@ func (fs *cachingFS) SetMtime(mtime time.Time) {
// LOCKS_EXCLUDED(fs.mu) // LOCKS_EXCLUDED(fs.mu)
func (fs *cachingFS) LookUpInode( func (fs *cachingFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) { op *fuseops.LookUpInodeOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -294,6 +297,7 @@ func (fs *cachingFS) LookUpInode(
// LOCKS_EXCLUDED(fs.mu) // LOCKS_EXCLUDED(fs.mu)
func (fs *cachingFS) GetInodeAttributes( func (fs *cachingFS) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) (err error) { op *fuseops.GetInodeAttributesOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -323,11 +327,13 @@ func (fs *cachingFS) GetInodeAttributes(
} }
func (fs *cachingFS) OpenDir( func (fs *cachingFS) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) (err error) { op *fuseops.OpenDirOp) (err error) {
return return
} }
func (fs *cachingFS) OpenFile( func (fs *cachingFS) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) (err error) { op *fuseops.OpenFileOp) (err error) {
return return
} }

View File

@ -19,6 +19,8 @@ import (
"os" "os"
"sync" "sync"
"golang.org/x/net/context"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/fuse/fuseutil"
@ -93,6 +95,7 @@ func (fs *flushFS) barAttributes() fuseops.InodeAttributes {
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
func (fs *flushFS) LookUpInode( func (fs *flushFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) { op *fuseops.LookUpInodeOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -126,6 +129,7 @@ func (fs *flushFS) LookUpInode(
} }
func (fs *flushFS) GetInodeAttributes( func (fs *flushFS) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) (err error) { op *fuseops.GetInodeAttributesOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -150,6 +154,7 @@ func (fs *flushFS) GetInodeAttributes(
} }
func (fs *flushFS) OpenFile( func (fs *flushFS) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) (err error) { op *fuseops.OpenFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -164,6 +169,7 @@ func (fs *flushFS) OpenFile(
} }
func (fs *flushFS) ReadFile( func (fs *flushFS) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) (err error) { op *fuseops.ReadFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -181,6 +187,7 @@ func (fs *flushFS) ReadFile(
} }
func (fs *flushFS) WriteFile( func (fs *flushFS) WriteFile(
ctx context.Context,
op *fuseops.WriteFileOp) (err error) { op *fuseops.WriteFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -204,6 +211,7 @@ func (fs *flushFS) WriteFile(
} }
func (fs *flushFS) SyncFile( func (fs *flushFS) SyncFile(
ctx context.Context,
op *fuseops.SyncFileOp) (err error) { op *fuseops.SyncFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -213,6 +221,7 @@ func (fs *flushFS) SyncFile(
} }
func (fs *flushFS) FlushFile( func (fs *flushFS) FlushFile(
ctx context.Context,
op *fuseops.FlushFileOp) (err error) { op *fuseops.FlushFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -222,6 +231,7 @@ func (fs *flushFS) FlushFile(
} }
func (fs *flushFS) OpenDir( func (fs *flushFS) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) (err error) { op *fuseops.OpenDirOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -240,6 +250,7 @@ func (fs *flushFS) OpenDir(
} }
func (fs *flushFS) ReadDir( func (fs *flushFS) ReadDir(
ctx context.Context,
op *fuseops.ReadDirOp) (err error) { op *fuseops.ReadDirOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()

View File

@ -18,6 +18,8 @@ import (
"fmt" "fmt"
"os" "os"
"golang.org/x/net/context"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/fuse/fuseutil"
@ -223,6 +225,7 @@ func (fs *fsImpl) findInodeByID(id fuseops.InodeID) (in *inode) {
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
func (fs *fsImpl) LookUpInode( func (fs *fsImpl) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) { op *fuseops.LookUpInodeOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -258,6 +261,7 @@ func (fs *fsImpl) LookUpInode(
} }
func (fs *fsImpl) GetInodeAttributes( func (fs *fsImpl) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) (err error) { op *fuseops.GetInodeAttributesOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -272,6 +276,7 @@ func (fs *fsImpl) GetInodeAttributes(
} }
func (fs *fsImpl) ForgetInode( func (fs *fsImpl) ForgetInode(
ctx context.Context,
op *fuseops.ForgetInodeOp) (err error) { op *fuseops.ForgetInodeOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -284,6 +289,7 @@ func (fs *fsImpl) ForgetInode(
} }
func (fs *fsImpl) MkDir( func (fs *fsImpl) MkDir(
ctx context.Context,
op *fuseops.MkDirOp) (err error) { op *fuseops.MkDirOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -315,6 +321,7 @@ func (fs *fsImpl) MkDir(
} }
func (fs *fsImpl) CreateFile( func (fs *fsImpl) CreateFile(
ctx context.Context,
op *fuseops.CreateFileOp) (err error) { op *fuseops.CreateFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -346,6 +353,7 @@ func (fs *fsImpl) CreateFile(
} }
func (fs *fsImpl) OpenFile( func (fs *fsImpl) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) (err error) { op *fuseops.OpenFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -357,6 +365,7 @@ func (fs *fsImpl) OpenFile(
} }
func (fs *fsImpl) OpenDir( func (fs *fsImpl) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) (err error) { op *fuseops.OpenDirOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()

View File

@ -19,6 +19,8 @@ import (
"os" "os"
"strings" "strings"
"golang.org/x/net/context"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/fuse/fuseutil"
@ -147,7 +149,9 @@ func (fs *helloFS) patchAttributes(
attr.Crtime = now attr.Crtime = now
} }
func (fs *helloFS) LookUpInode(op *fuseops.LookUpInodeOp) (err error) { func (fs *helloFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) {
// Find the info for the parent. // Find the info for the parent.
parentInfo, ok := gInodeInfo[op.Parent] parentInfo, ok := gInodeInfo[op.Parent]
if !ok { if !ok {
@ -172,6 +176,7 @@ func (fs *helloFS) LookUpInode(op *fuseops.LookUpInodeOp) (err error) {
} }
func (fs *helloFS) GetInodeAttributes( func (fs *helloFS) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) (err error) { op *fuseops.GetInodeAttributesOp) (err error) {
// Find the info for this inode. // Find the info for this inode.
info, ok := gInodeInfo[op.Inode] info, ok := gInodeInfo[op.Inode]
@ -190,12 +195,14 @@ func (fs *helloFS) GetInodeAttributes(
} }
func (fs *helloFS) OpenDir( func (fs *helloFS) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) (err error) { op *fuseops.OpenDirOp) (err error) {
// Allow opening any directory. // Allow opening any directory.
return return
} }
func (fs *helloFS) ReadDir( func (fs *helloFS) ReadDir(
ctx context.Context,
op *fuseops.ReadDirOp) (err error) { op *fuseops.ReadDirOp) (err error) {
// Find the info for this inode. // Find the info for this inode.
info, ok := gInodeInfo[op.Inode] info, ok := gInodeInfo[op.Inode]
@ -232,12 +239,14 @@ func (fs *helloFS) ReadDir(
} }
func (fs *helloFS) OpenFile( func (fs *helloFS) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) (err error) { op *fuseops.OpenFileOp) (err error) {
// Allow opening any file. // Allow opening any file.
return return
} }
func (fs *helloFS) ReadFile( func (fs *helloFS) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) (err error) { op *fuseops.ReadFileOp) (err error) {
// Let io.ReaderAt deal with the semantics. // Let io.ReaderAt deal with the semantics.
reader := strings.NewReader("Hello, world!") reader := strings.NewReader("Hello, world!")

View File

@ -19,6 +19,8 @@ import (
"os" "os"
"sync" "sync"
"golang.org/x/net/context"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/fuse/fuseutil"
@ -78,6 +80,7 @@ func (fs *InterruptFS) WaitForReadInFlight() {
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
func (fs *InterruptFS) LookUpInode( func (fs *InterruptFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) { op *fuseops.LookUpInodeOp) (err error) {
// We support only one parent. // We support only one parent.
if op.Parent != fuseops.RootInodeID { if op.Parent != fuseops.RootInodeID {
@ -99,6 +102,7 @@ func (fs *InterruptFS) LookUpInode(
} }
func (fs *InterruptFS) GetInodeAttributes( func (fs *InterruptFS) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) (err error) { op *fuseops.GetInodeAttributesOp) (err error) {
switch op.Inode { switch op.Inode {
case fuseops.RootInodeID: case fuseops.RootInodeID:
@ -116,11 +120,13 @@ func (fs *InterruptFS) GetInodeAttributes(
} }
func (fs *InterruptFS) OpenFile( func (fs *InterruptFS) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) (err error) { op *fuseops.OpenFileOp) (err error) {
return return
} }
func (fs *InterruptFS) ReadFile( func (fs *InterruptFS) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) (err error) { op *fuseops.ReadFileOp) (err error) {
// Signal that a read has been received. // Signal that a read has been received.
fs.mu.Lock() fs.mu.Lock()
@ -129,7 +135,7 @@ func (fs *InterruptFS) ReadFile(
fs.mu.Unlock() fs.mu.Unlock()
// Wait for cancellation. // Wait for cancellation.
done := op.Context().Done() done := ctx.Done()
if done == nil { if done == nil {
panic("Expected non-nil channel.") panic("Expected non-nil channel.")
} }
@ -137,7 +143,7 @@ func (fs *InterruptFS) ReadFile(
<-done <-done
// Return the context's error. // Return the context's error.
err = op.Context().Err() err = ctx.Err()
return return
} }

View File

@ -20,6 +20,8 @@ import (
"os" "os"
"time" "time"
"golang.org/x/net/context"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/fuse/fuseutil"
@ -190,6 +192,7 @@ func (fs *memFS) deallocateInode(id fuseops.InodeID) {
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
func (fs *memFS) LookUpInode( func (fs *memFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) { op *fuseops.LookUpInodeOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -220,6 +223,7 @@ func (fs *memFS) LookUpInode(
} }
func (fs *memFS) GetInodeAttributes( func (fs *memFS) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) (err error) { op *fuseops.GetInodeAttributesOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -238,6 +242,7 @@ func (fs *memFS) GetInodeAttributes(
} }
func (fs *memFS) SetInodeAttributes( func (fs *memFS) SetInodeAttributes(
ctx context.Context,
op *fuseops.SetInodeAttributesOp) (err error) { op *fuseops.SetInodeAttributesOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -259,6 +264,7 @@ func (fs *memFS) SetInodeAttributes(
} }
func (fs *memFS) MkDir( func (fs *memFS) MkDir(
ctx context.Context,
op *fuseops.MkDirOp) (err error) { op *fuseops.MkDirOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -301,6 +307,7 @@ func (fs *memFS) MkDir(
} }
func (fs *memFS) CreateFile( func (fs *memFS) CreateFile(
ctx context.Context,
op *fuseops.CreateFileOp) (err error) { op *fuseops.CreateFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -350,6 +357,7 @@ func (fs *memFS) CreateFile(
} }
func (fs *memFS) CreateSymlink( func (fs *memFS) CreateSymlink(
ctx context.Context,
op *fuseops.CreateSymlinkOp) (err error) { op *fuseops.CreateSymlinkOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -400,6 +408,7 @@ func (fs *memFS) CreateSymlink(
} }
func (fs *memFS) Rename( func (fs *memFS) Rename(
ctx context.Context,
op *fuseops.RenameOp) (err error) { op *fuseops.RenameOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -440,6 +449,7 @@ func (fs *memFS) Rename(
} }
func (fs *memFS) RmDir( func (fs *memFS) RmDir(
ctx context.Context,
op *fuseops.RmDirOp) (err error) { op *fuseops.RmDirOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -473,6 +483,7 @@ func (fs *memFS) RmDir(
} }
func (fs *memFS) Unlink( func (fs *memFS) Unlink(
ctx context.Context,
op *fuseops.UnlinkOp) (err error) { op *fuseops.UnlinkOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -500,6 +511,7 @@ func (fs *memFS) Unlink(
} }
func (fs *memFS) OpenDir( func (fs *memFS) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) (err error) { op *fuseops.OpenDirOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -517,6 +529,7 @@ func (fs *memFS) OpenDir(
} }
func (fs *memFS) ReadDir( func (fs *memFS) ReadDir(
ctx context.Context,
op *fuseops.ReadDirOp) (err error) { op *fuseops.ReadDirOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -531,6 +544,7 @@ func (fs *memFS) ReadDir(
} }
func (fs *memFS) OpenFile( func (fs *memFS) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) (err error) { op *fuseops.OpenFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -548,6 +562,7 @@ func (fs *memFS) OpenFile(
} }
func (fs *memFS) ReadFile( func (fs *memFS) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) (err error) { op *fuseops.ReadFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -569,6 +584,7 @@ func (fs *memFS) ReadFile(
} }
func (fs *memFS) WriteFile( func (fs *memFS) WriteFile(
ctx context.Context,
op *fuseops.WriteFileOp) (err error) { op *fuseops.WriteFileOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -583,6 +599,7 @@ func (fs *memFS) WriteFile(
} }
func (fs *memFS) ReadSymlink( func (fs *memFS) ReadSymlink(
ctx context.Context,
op *fuseops.ReadSymlinkOp) (err error) { op *fuseops.ReadSymlinkOp) (err error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()