Compare commits

..

4 Commits

Author SHA1 Message Date
Vitaliy Filippov 2be4ecc37d Fix examples to use vectored read 2022-02-01 21:15:00 +03:00
Vitaliy Filippov ac82ada21e Add vectored read to readbenchfs
You can now run `./readbenchfs --mount_point dir --vectored` and then
`dd if=dir/test of=/dev/null iflag=direct bs=1M status=progress` to test
vectored read speed.

My results with GOMAXPROCS=1:
- Before vectored read patch: 390 MB/s read
- Non-vectored read after vectored read patch: 830 MB/s read
- Vectored read: 1200 MB/s read
2022-02-01 21:14:59 +03:00
Vitaliy Filippov 694c1bf9db Introduce VectoredReadOp
Read requests can now take vectored responses from the filesystem
implementation and send them to FUSE device via the writev() system call.

This allows file systems to send data without copying it into the
library-provided buffer if the data is already in memory.

The change also speeds up normal ReadFileOps as a side effect because
it removes extra memory allocations.
2022-02-01 21:14:40 +03:00
Vitaliy Filippov b5cbfcd8b6 Add ReadBenchFS to test linear read speed 2022-02-01 21:14:38 +03:00
51 changed files with 845 additions and 2370 deletions

View File

@ -17,7 +17,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2.1.4 uses: actions/setup-go@v2.1.4
with: with:
go-version: ^1.19 go-version: 1.16
id: go id: go
# Check the codebase has been formatted by `gofmt`. # Check the codebase has been formatted by `gofmt`.
# We run two `gofmt` commands for different purposes: # We run two `gofmt` commands for different purposes:
@ -38,7 +38,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2.1.4 uses: actions/setup-go@v2.1.4
with: with:
go-version: ^1.19 go-version: 1.16
id: go id: go
- name: Install fuse - name: Install fuse
run: sudo apt-get update && sudo apt-get install -y fuse3 libfuse-dev run: sudo apt-get update && sudo apt-get install -y fuse3 libfuse-dev
@ -56,7 +56,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2.1.4 uses: actions/setup-go@v2.1.4
with: with:
go-version: ^1.19 go-version: 1.16
id: go id: go
- name: Install macfuse - name: Install macfuse
run: HOMEBREW_NO_AUTO_UPDATE=1 brew install macfuse run: HOMEBREW_NO_AUTO_UPDATE=1 brew install macfuse

View File

@ -39,19 +39,17 @@ var contextKey interface{} = contextKeyType(0)
// //
// As of 2015-03-26, the behavior in the kernel is: // As of 2015-03-26, the behavior in the kernel is:
// //
// - (https://tinyurl.com/2eakn5e9, https://tinyurl.com/mry9e33d) Set the // * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
// local variable ra_pages to be init_response->max_readahead divided by // ra_pages to be init_response->max_readahead divided by the page size.
// the page size.
// //
// - (https://tinyurl.com/2eakn5e9, https://tinyurl.com/mbpshk8h) Set // * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
// backing_dev_info::ra_pages to the min of that value and what was sent in // backing_dev_info::ra_pages to the min of that value and what was sent
// the request's max_readahead field. // in the request's max_readahead field.
// //
// - (https://tinyurl.com/57hpfu4x) Use backing_dev_info::ra_pages when // * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
// deciding how much to read ahead. // how much to read ahead.
// //
// - (https://tinyurl.com/ywhfcfte) Don't read ahead at all if that field is // * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
// zero.
// //
// Reading a page at a time is a drag. Ask for a larger size. // Reading a page at a time is a drag. Ask for a larger size.
const maxReadahead = 1 << 20 const maxReadahead = 1 << 20
@ -153,7 +151,6 @@ func (c *Connection) Init() error {
cacheSymlinks := initOp.Flags&fusekernel.InitCacheSymlinks > 0 cacheSymlinks := initOp.Flags&fusekernel.InitCacheSymlinks > 0
noOpenSupport := initOp.Flags&fusekernel.InitNoOpenSupport > 0 noOpenSupport := initOp.Flags&fusekernel.InitNoOpenSupport > 0
noOpendirSupport := initOp.Flags&fusekernel.InitNoOpendirSupport > 0 noOpendirSupport := initOp.Flags&fusekernel.InitNoOpendirSupport > 0
readdirplusSupport := initOp.Flags&fusekernel.InitDoReaddirplus > 0
// Respond to the init op. // Respond to the init op.
initOp.Library = c.protocol initOp.Library = c.protocol
@ -196,17 +193,8 @@ func (c *Connection) Init() error {
initOp.Flags |= fusekernel.InitNoOpendirSupport initOp.Flags |= fusekernel.InitNoOpendirSupport
} }
// Tell the Kernel to allow sending parallel lookup and readdir operations. c.Reply(ctx, nil)
if c.cfg.EnableParallelDirOps { return nil
initOp.Flags |= fusekernel.InitParallelDirOps
}
// Tell the kernel to do readdirplus (readdir+lookup in one call)
if c.cfg.UseReadDirPlus && readdirplusSupport {
initOp.Flags |= fusekernel.InitDoReaddirplus
}
return c.Reply(ctx, nil)
} }
// Log information for an operation with the given ID. calldepth is the depth // Log information for an operation with the given ID. calldepth is the depth
@ -321,13 +309,13 @@ func (c *Connection) handleInterrupt(fuseID uint64) {
defer c.mu.Unlock() defer c.mu.Unlock()
// NOTE(jacobsa): fuse.txt in the Linux kernel documentation // NOTE(jacobsa): fuse.txt in the Linux kernel documentation
// (https://tinyurl.com/2r4ajuwd) defines the kernel <-> userspace protocol // (https://goo.gl/H55Dnr) defines the kernel <-> userspace protocol for
// for interrupts. // interrupts.
// //
// In particular, my reading of it is that an interrupt request cannot be // In particular, my reading of it is that an interrupt request cannot be
// delivered to userspace before the original request. The part about the // delivered to userspace before the original request. The part about the
// race and EAGAIN appears to be aimed at userspace programs that // race and EAGAIN appears to be aimed at userspace programs that
// concurrently process requests (https://tinyurl.com/3euehwfb). // concurrently process requests (cf. http://goo.gl/BES2rs).
// //
// So in this method if we can't find the ID to be interrupted, it means that // So in this method if we can't find the ID to be interrupted, it means that
// the request has already been replied to. // the request has already been replied to.
@ -381,33 +369,18 @@ func (c *Connection) readMessage() (*buffer.InMessage, error) {
} }
// Write the supplied message to the kernel. // Write the supplied message to the kernel.
func (c *Connection) writeMessage(outMsg *buffer.OutMessage) error { func (c *Connection) writeMessage(msg []byte) error {
var err error // Avoid the retry loop in os.File.Write.
var n int n, err := syscall.Write(int(c.dev.Fd()), msg)
expectedLen := outMsg.Len()
if outMsg.Sglist != nil {
if fusekernel.IsPlatformFuseT {
// writev is not atomic on macos, restrict to fuse-t platform
writeLock.Lock()
defer writeLock.Unlock()
}
n, err = writev(int(c.dev.Fd()), outMsg.Sglist)
} else {
// Avoid the retry loop in os.File.Write.
n, err = syscall.Write(int(c.dev.Fd()), outMsg.OutHeaderBytes())
}
if err == nil && n != expectedLen {
err = fmt.Errorf("Wrote %d bytes; expected %d", n, expectedLen)
}
if err != nil { if err != nil {
writeErrMsg := fmt.Sprintf("writeMessage: %v %v", err, outMsg.OutHeaderBytes()) return err
if c.errorLogger != nil {
c.errorLogger.Print(writeErrMsg)
}
return fmt.Errorf(writeErrMsg)
} }
outMsg.Sglist = nil
return err if n != len(msg) {
return fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg))
}
return nil
} }
// ReadOp consumes the next op from the kernel process, returning the op and a // ReadOp consumes the next op from the kernel process, returning the op and a
@ -481,7 +454,7 @@ func (c *Connection) shouldLogError(
return false return false
} }
case *fuseops.GetXattrOp, *fuseops.ListXattrOp: case *fuseops.GetXattrOp, *fuseops.ListXattrOp:
if err == syscall.ENOSYS || err == syscall.ENODATA || err == syscall.ERANGE { if err == syscall.ENODATA || err == syscall.ERANGE {
return false return false
} }
case *unknownOp: case *unknownOp:
@ -494,13 +467,11 @@ func (c *Connection) shouldLogError(
return true return true
} }
var writeLock sync.Mutex
// Reply replies to an op previously read using ReadOp, with the supplied error // Reply replies to an op previously read using ReadOp, with the supplied error
// (or nil if successful). The context must be the context returned by ReadOp. // (or nil if successful). The context must be the context returned by ReadOp.
// //
// LOCKS_EXCLUDED(c.mu) // LOCKS_EXCLUDED(c.mu)
func (c *Connection) Reply(ctx context.Context, opErr error) error { func (c *Connection) Reply(ctx context.Context, opErr error) {
// Extract the state we stuffed in earlier. // Extract the state we stuffed in earlier.
var key interface{} = contextKey var key interface{} = contextKey
foo := ctx.Value(key) foo := ctx.Value(key)
@ -514,25 +485,9 @@ func (c *Connection) Reply(ctx context.Context, opErr error) error {
outMsg := state.outMsg outMsg := state.outMsg
fuseID := inMsg.Header().Unique fuseID := inMsg.Header().Unique
defer func() { // Make sure we destroy the messages when we're done.
// Invoke any callbacks set by the FUSE server after the response to the kernel is defer c.putInMessage(inMsg)
// complete and before the inMessage and outMessage memory buffers have been freed. defer c.putOutMessage(outMsg)
callback := c.callbackForOp(op)
if callback != nil {
callback()
}
// Make sure we destroy the messages when we're done.
suppressReuse := false
if wr, ok := op.(*fuseops.WriteFileOp); ok {
suppressReuse = wr.SuppressReuse
}
if !suppressReuse {
c.putInMessage(inMsg)
}
c.putOutMessage(outMsg)
}()
// Clean up state for this op. // Clean up state for this op.
c.finishOp(inMsg.Header().Opcode, inMsg.Header().Unique) c.finishOp(inMsg.Header().Opcode, inMsg.Header().Unique)
@ -540,7 +495,7 @@ func (c *Connection) Reply(ctx context.Context, opErr error) error {
// Debug logging // Debug logging
if c.debugLogger != nil { if c.debugLogger != nil {
if opErr == nil { if opErr == nil {
c.debugLog(fuseID, 1, "-> %s", describeResponse(op)) c.debugLog(fuseID, 1, "-> OK (%s)", describeResponse(op))
} else { } else {
c.debugLog(fuseID, 1, "-> Error: %q", opErr.Error()) c.debugLog(fuseID, 1, "-> Error: %q", opErr.Error())
} }
@ -555,31 +510,17 @@ func (c *Connection) Reply(ctx context.Context, opErr error) error {
noResponse := c.kernelResponse(outMsg, inMsg.Header().Unique, op, opErr) noResponse := c.kernelResponse(outMsg, inMsg.Header().Unique, op, opErr)
if !noResponse { if !noResponse {
c.writeMessage(outMsg) var err error
if outMsg.Sglist != nil {
_, err = writev(int(c.dev.Fd()), outMsg.Sglist)
} else {
err = c.writeMessage(outMsg.OutHeaderBytes())
}
if err != nil && c.errorLogger != nil {
c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.OutHeaderBytes())
}
outMsg.Sglist = nil
} }
return nil
}
func (c *Connection) callbackForOp(op interface{}) func() {
switch o := op.(type) {
case *fuseops.ReadFileOp:
return o.Callback
case *fuseops.WriteFileOp:
return o.Callback
}
return nil
}
// Send a notification to the kernel
// notification must be a pointer to one of fuseops.NotifyXXX structures
// To avoid a deadlock notifications must not be called in the execution path of a related filesytem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.
func (c *Connection) Notify(notification interface{}) error {
outMsg := c.getOutMessage()
defer c.putOutMessage(outMsg)
c.kernelNotification(outMsg, notification)
outMsg.OutHeader().Len = uint32(outMsg.Len())
return c.writeMessage(outMsg)
} }
// Close the connection. Must not be called until operations that were read // Close the connection. Must not be called until operations that were read

View File

@ -51,25 +51,15 @@ func convertInMessage(
} }
o = &fuseops.LookUpInodeOp{ o = &fuseops.LookUpInodeOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid), Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(buf[:n-1]), Name: string(buf[:n-1]),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpGetattr: case fusekernel.OpGetattr:
o = &fuseops.GetInodeAttributesOp{ o = &fuseops.GetInodeAttributesOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpSetattr: case fusekernel.OpSetattr:
@ -80,31 +70,18 @@ func convertInMessage(
} }
to := &fuseops.SetInodeAttributesOp{ to := &fuseops.SetInodeAttributesOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
o = to o = to
valid := fusekernel.SetattrValid(in.Valid) valid := fusekernel.SetattrValid(in.Valid)
if valid&fusekernel.SetattrUid != 0 {
to.Uid = &in.Uid
}
if valid&fusekernel.SetattrGid != 0 {
to.Gid = &in.Gid
}
if valid&fusekernel.SetattrSize != 0 { if valid&fusekernel.SetattrSize != 0 {
to.Size = &in.Size to.Size = &in.Size
} }
if valid&fusekernel.SetattrMode != 0 { if valid&fusekernel.SetattrMode != 0 {
mode := fuseops.ConvertFileMode(in.Mode) mode := convertFileMode(in.Mode)
to.Mode = &mode to.Mode = &mode
} }
@ -131,45 +108,9 @@ func convertInMessage(
} }
o = &fuseops.ForgetInodeOp{ o = &fuseops.ForgetInodeOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
N: in.Nlookup, N: in.Nlookup,
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
}
case fusekernel.OpBatchForget:
type input fusekernel.BatchForgetCountIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpBatchForget")
}
entries := make([]fuseops.BatchForgetEntry, 0, in.Count)
for i := uint32(0); i < in.Count; i++ {
type entry fusekernel.BatchForgetEntryIn
ein := (*entry)(inMsg.Consume(unsafe.Sizeof(entry{})))
if ein == nil {
return nil, errors.New("Corrupt OpBatchForget")
}
entries = append(entries, fuseops.BatchForgetEntry{
Inode: fuseops.InodeID(ein.Inode),
N: ein.Nlookup,
})
}
o = &fuseops.BatchForgetOp{
Entries: entries,
OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpMkdir: case fusekernel.OpMkdir:
@ -190,18 +131,13 @@ func convertInMessage(
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 (https://tinyurl.com/3djx8498), and // permissions and sticky bits set (cf. https://goo.gl/WxgQXk), and fuse
// fuse passes that on directly (https://tinyurl.com/exezw647). In other // passes that on directly (cf. https://goo.gl/f31aMo). In other words,
// words, the fact that this is a directory is implicit in the fact that // the fact that this is a directory is implicit in the fact that the
// the opcode is mkdir. But we want the correct mode to go through, so // opcode is mkdir. But we want the correct mode to go through, so ensure
// ensure that os.ModeDir is set. // that os.ModeDir is set.
Mode: fuseops.ConvertFileMode(in.Mode) | os.ModeDir, Mode: convertFileMode(in.Mode) | os.ModeDir,
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpMknod: case fusekernel.OpMknod:
@ -218,16 +154,10 @@ func convertInMessage(
name = name[:i] name = name[:i]
o = &fuseops.MkNodeOp{ o = &fuseops.MkNodeOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid), Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name), Name: string(name),
Mode: fuseops.ConvertFileMode(in.Mode), Mode: convertFileMode(in.Mode),
Rdev: in.Rdev, OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpCreate: case fusekernel.OpCreate:
@ -244,15 +174,10 @@ func convertInMessage(
name = name[:i] name = name[:i]
o = &fuseops.CreateFileOp{ o = &fuseops.CreateFileOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid), Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name), Name: string(name),
Mode: fuseops.ConvertFileMode(in.Mode), Mode: convertFileMode(in.Mode),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpSymlink: case fusekernel.OpSymlink:
@ -268,15 +193,10 @@ func convertInMessage(
newName, target := names[0:i], names[i+1:len(names)-1] newName, target := names[0:i], names[i+1:len(names)-1]
o = &fuseops.CreateSymlinkOp{ o = &fuseops.CreateSymlinkOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid), Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(newName), Name: string(newName),
Target: string(target), Target: string(target),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpRename: case fusekernel.OpRename:
@ -318,12 +238,7 @@ func convertInMessage(
OldName: string(oldName), OldName: string(oldName),
NewParent: fuseops.InodeID(in.Newdir), NewParent: fuseops.InodeID(in.Newdir),
NewName: string(newName), NewName: string(newName),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpUnlink: case fusekernel.OpUnlink:
@ -334,14 +249,9 @@ func convertInMessage(
} }
o = &fuseops.UnlinkOp{ o = &fuseops.UnlinkOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid), Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(buf[:n-1]), Name: string(buf[:n-1]),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpRmdir: case fusekernel.OpRmdir:
@ -352,43 +262,21 @@ func convertInMessage(
} }
o = &fuseops.RmDirOp{ o = &fuseops.RmDirOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid), Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(buf[:n-1]), Name: string(buf[:n-1]),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpOpen: case fusekernel.OpOpen:
type input fusekernel.OpenIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpOpen")
}
o = &fuseops.OpenFileOp{ o = &fuseops.OpenFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
OpenFlags: fusekernel.OpenFlags(in.Flags), OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpOpendir: case fusekernel.OpOpendir:
o = &fuseops.OpenDirOp{ o = &fuseops.OpenDirOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpRead: case fusekernel.OpRead:
@ -397,27 +285,29 @@ func convertInMessage(
return nil, errors.New("Corrupt OpRead") return nil, errors.New("Corrupt OpRead")
} }
to := &fuseops.ReadFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Offset: int64(in.Offset),
Size: int64(in.Size),
OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
}
if !config.UseVectoredRead { if !config.UseVectoredRead {
// Use part of the incoming message storage as the read buffer // Use part of the incoming message storage as the read buffer
// For vectored zero-copy reads, don't allocate any buffers buf := inMsg.GetFree(int(in.Size))
to.Dst = inMsg.GetFree(int(in.Size)) to := &fuseops.ReadFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Offset: int64(in.Offset),
Dst: buf,
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
o = to
} else {
// Don't allocate any buffers when zero-copy is used
to := &fuseops.VectoredReadOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Offset: int64(in.Offset),
Size: int64(in.Size),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
o = to
} }
o = to
case fusekernel.OpReaddirplus:
fallthrough
case fusekernel.OpReaddir: case fusekernel.OpReaddir:
in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol))) in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol)))
if in == nil { if in == nil {
@ -425,16 +315,10 @@ func convertInMessage(
} }
to := &fuseops.ReadDirOp{ to := &fuseops.ReadDirOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
Offset: fuseops.DirOffset(in.Offset), Offset: fuseops.DirOffset(in.Offset),
Plus: inMsg.Header().Opcode == fusekernel.OpReaddirplus, OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
o = to o = to
@ -457,13 +341,8 @@ func convertInMessage(
} }
o = &fuseops.ReleaseFileHandleOp{ o = &fuseops.ReleaseFileHandleOp{
Handle: fuseops.HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpReleasedir: case fusekernel.OpReleasedir:
@ -474,13 +353,8 @@ func convertInMessage(
} }
o = &fuseops.ReleaseDirHandleOp{ o = &fuseops.ReleaseDirHandleOp{
Handle: fuseops.HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpWrite: case fusekernel.OpWrite:
@ -495,45 +369,23 @@ func convertInMessage(
} }
o = &fuseops.WriteFileOp{ o = &fuseops.WriteFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
Data: buf, Data: buf,
Offset: int64(in.Offset), Offset: int64(in.Offset),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpFsync, fusekernel.OpFsyncdir: case fusekernel.OpFsync, fusekernel.OpFsyncdir:
type input fusekernel.FsyncIn type input fusekernel.FsyncIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{}))) in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil { if in == nil {
return nil, errors.New("Corrupt OpFsync/OpFsyncdir") return nil, errors.New("Corrupt OpFsync")
} }
o = &fuseops.SyncFileOp{ o = &fuseops.SyncFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
}
case fusekernel.OpSyncFS:
type input fusekernel.SyncFSIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpSyncFS")
}
o = &fuseops.SyncFSOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid}, OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
} }
@ -545,25 +397,15 @@ func convertInMessage(
} }
o = &fuseops.FlushFileOp{ o = &fuseops.FlushFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpReadlink: case fusekernel.OpReadlink:
o = &fuseops.ReadSymlinkOp{ o = &fuseops.ReadSymlinkOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpStatfs: case fusekernel.OpStatfs:
@ -611,15 +453,10 @@ func convertInMessage(
} }
o = &fuseops.CreateLinkOp{ o = &fuseops.CreateLinkOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid), Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name), Name: string(name),
Target: fuseops.InodeID(in.Oldnodeid), Target: fuseops.InodeID(in.Oldnodeid),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpRemovexattr: case fusekernel.OpRemovexattr:
@ -630,14 +467,9 @@ func convertInMessage(
} }
o = &fuseops.RemoveXattrOp{ o = &fuseops.RemoveXattrOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(buf[:n-1]), Name: string(buf[:n-1]),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpGetxattr: case fusekernel.OpGetxattr:
@ -655,14 +487,9 @@ func convertInMessage(
name = name[:i] name = name[:i]
to := &fuseops.GetXattrOp{ to := &fuseops.GetXattrOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name), Name: string(name),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
o = to o = to
@ -689,13 +516,8 @@ func convertInMessage(
} }
to := &fuseops.ListXattrOp{ to := &fuseops.ListXattrOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
o = to o = to
@ -730,16 +552,11 @@ func convertInMessage(
name, value := payload[:i], payload[i+1:len(payload)] name, value := payload[:i], payload[i+1:len(payload)]
o = &fuseops.SetXattrOp{ o = &fuseops.SetXattrOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name), Name: string(name),
Value: value, Value: value,
Flags: in.Flags, Flags: in.Flags,
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpFallocate: case fusekernel.OpFallocate:
type input fusekernel.FallocateIn type input fusekernel.FallocateIn
@ -749,52 +566,11 @@ func convertInMessage(
} }
o = &fuseops.FallocateOp{ o = &fuseops.FallocateOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Offset: in.Offset,
Length: in.Length,
Mode: in.Mode,
OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
}
case fusekernel.OpPoll:
type input fusekernel.PollIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpPoll")
}
o = &fuseops.PollOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid), Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
Kh: in.Kh,
Flags: fusekernel.PollFlags(in.Flags),
Events: fusekernel.PollEvents(in.Events),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
case fusekernel.OpNotifyReply:
type input fusekernel.NotifyRetrieveIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpNotifyReply")
}
buf := inMsg.ConsumeBytes(inMsg.Len())
if len(buf) < int(in.Size) {
return nil, errors.New("Corrupt OpNotifyReply")
}
o = &fuseops.NotifyRetrieveReplyOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Unique: inMsg.Header().Unique,
Offset: in.Offset, Offset: in.Offset,
Length: in.Size, Length: in.Length,
Mode: in.Mode,
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid}, OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
} }
@ -828,12 +604,6 @@ func (c *Connection) kernelResponse(
case *fuseops.ForgetInodeOp: case *fuseops.ForgetInodeOp:
return true return true
case *fuseops.BatchForgetOp:
return true
case *fuseops.NotifyRetrieveReplyOp:
return true
case *interruptOp: case *interruptOp:
return true return true
} }
@ -877,37 +647,37 @@ func (c *Connection) kernelResponseForOp(
case *fuseops.LookUpInodeOp: case *fuseops.LookUpInodeOp:
size := int(fusekernel.EntryOutSize(c.protocol)) size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size)) out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out) convertChildInodeEntry(&o.Entry, out)
case *fuseops.GetInodeAttributesOp: case *fuseops.GetInodeAttributesOp:
size := int(fusekernel.AttrOutSize(c.protocol)) size := int(fusekernel.AttrOutSize(c.protocol))
out := (*fusekernel.AttrOut)(m.Grow(size)) out := (*fusekernel.AttrOut)(m.Grow(size))
out.AttrValid, out.AttrValidNsec = fuseops.ConvertExpirationTime( out.AttrValid, out.AttrValidNsec = convertExpirationTime(
o.AttributesExpiration) o.AttributesExpiration)
fuseops.ConvertAttributes(o.Inode, &o.Attributes, &out.Attr) convertAttributes(o.Inode, &o.Attributes, &out.Attr)
case *fuseops.SetInodeAttributesOp: case *fuseops.SetInodeAttributesOp:
size := int(fusekernel.AttrOutSize(c.protocol)) size := int(fusekernel.AttrOutSize(c.protocol))
out := (*fusekernel.AttrOut)(m.Grow(size)) out := (*fusekernel.AttrOut)(m.Grow(size))
out.AttrValid, out.AttrValidNsec = fuseops.ConvertExpirationTime( out.AttrValid, out.AttrValidNsec = convertExpirationTime(
o.AttributesExpiration) o.AttributesExpiration)
fuseops.ConvertAttributes(o.Inode, &o.Attributes, &out.Attr) convertAttributes(o.Inode, &o.Attributes, &out.Attr)
case *fuseops.MkDirOp: case *fuseops.MkDirOp:
size := int(fusekernel.EntryOutSize(c.protocol)) size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size)) out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out) convertChildInodeEntry(&o.Entry, out)
case *fuseops.MkNodeOp: case *fuseops.MkNodeOp:
size := int(fusekernel.EntryOutSize(c.protocol)) size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size)) out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out) convertChildInodeEntry(&o.Entry, out)
case *fuseops.CreateFileOp: case *fuseops.CreateFileOp:
eSize := int(fusekernel.EntryOutSize(c.protocol)) eSize := int(fusekernel.EntryOutSize(c.protocol))
e := (*fusekernel.EntryOut)(m.Grow(eSize)) e := (*fusekernel.EntryOut)(m.Grow(eSize))
fuseops.ConvertChildInodeEntry(&o.Entry, e) convertChildInodeEntry(&o.Entry, e)
oo := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{})))) oo := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{}))))
oo.Fh = uint64(o.Handle) oo.Fh = uint64(o.Handle)
@ -915,12 +685,12 @@ func (c *Connection) kernelResponseForOp(
case *fuseops.CreateSymlinkOp: case *fuseops.CreateSymlinkOp:
size := int(fusekernel.EntryOutSize(c.protocol)) size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size)) out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out) convertChildInodeEntry(&o.Entry, out)
case *fuseops.CreateLinkOp: case *fuseops.CreateLinkOp:
size := int(fusekernel.EntryOutSize(c.protocol)) size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size)) out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out) convertChildInodeEntry(&o.Entry, out)
case *fuseops.RenameOp: case *fuseops.RenameOp:
// Empty response // Empty response
@ -935,14 +705,6 @@ func (c *Connection) kernelResponseForOp(
out := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{})))) out := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{}))))
out.Fh = uint64(o.Handle) out.Fh = uint64(o.Handle)
if o.CacheDir {
out.OpenFlags |= uint32(fusekernel.OpenCacheDir)
}
if o.KeepCache {
out.OpenFlags |= uint32(fusekernel.OpenKeepCache)
}
case *fuseops.ReadDirOp: case *fuseops.ReadDirOp:
// convertInMessage already set up the destination buffer to be at the end // convertInMessage already set up the destination buffer to be at the end
// of the out message. We need only shrink to the right size based on how // of the out message. We need only shrink to the right size based on how
@ -965,11 +727,11 @@ func (c *Connection) kernelResponseForOp(
} }
case *fuseops.ReadFileOp: case *fuseops.ReadFileOp:
if o.Dst != nil { m.Append(o.Dst)
m.Append(o.Dst) m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
} else {
m.Append(o.Data...) case *fuseops.VectoredReadOp:
} m.Append(o.Data...)
m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead) m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
case *fuseops.WriteFileOp: case *fuseops.WriteFileOp:
@ -997,7 +759,7 @@ func (c *Connection) kernelResponseForOp(
out.St.Ffree = o.InodesFree out.St.Ffree = o.InodesFree
out.St.Namelen = 255 out.St.Namelen = 255
// The posix spec for sys/statvfs.h (https://tinyurl.com/2juj6ah6) defines the // The posix spec for sys/statvfs.h (http://goo.gl/LktgrF) defines the
// following fields of statvfs, among others: // following fields of statvfs, among others:
// //
// f_bsize File system block size. // f_bsize File system block size.
@ -1007,7 +769,7 @@ func (c *Connection) kernelResponseForOp(
// It appears as though f_bsize was the only thing supported by most unixes // It appears as though f_bsize was the only thing supported by most unixes
// originally, but then f_frsize was added when new sorts of file systems // originally, but then f_frsize was added when new sorts of file systems
// came about. Quoth The Linux Programming Interface by Michael Kerrisk // came about. Quoth The Linux Programming Interface by Michael Kerrisk
// (https://tinyurl.com/5n8mjtws): // (https://goo.gl/5LZMxQ):
// //
// For most Linux file systems, the values of f_bsize and f_frsize are // For most Linux file systems, the values of f_bsize and f_frsize are
// the same. However, some file systems support the notion of block // the same. However, some file systems support the notion of block
@ -1051,9 +813,6 @@ func (c *Connection) kernelResponseForOp(
case *fuseops.FallocateOp: case *fuseops.FallocateOp:
// Empty response // Empty response
case *fuseops.SyncFSOp:
// Empty response
case *initOp: case *initOp:
out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{})))) out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{}))))
@ -1068,13 +827,6 @@ func (c *Connection) kernelResponseForOp(
out.TimeGran = 1 out.TimeGran = 1
out.MaxPages = o.MaxPages out.MaxPages = o.MaxPages
case *fuseops.PollOp:
out := (*fusekernel.PollOut)(m.Grow(int(unsafe.Sizeof(fusekernel.PollOut{}))))
out.Revents = uint32(o.Revents)
case *fuseops.NotifyRetrieveReplyOp:
// Empty response
default: default:
panic(fmt.Sprintf("Unexpected op: %#v", op)) panic(fmt.Sprintf("Unexpected op: %#v", op))
} }
@ -1082,73 +834,115 @@ func (c *Connection) kernelResponseForOp(
return return
} }
// Like kernelResponse, but assumes the user replied with a nil error to the op.
func (c *Connection) kernelNotification(
m *buffer.OutMessage,
op interface{}) {
h := m.OutHeader()
h.Unique = 0
// Create the appropriate output message
switch o := op.(type) {
case *fuseops.NotifyPollWakeup:
h.Error = fusekernel.NotifyCodePoll
out := (*fusekernel.NotifyPollWakeupOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyPollWakeupOut{}))))
out.Kh = o.Kh
case *fuseops.NotifyInvalInode:
h.Error = fusekernel.NotifyCodeInvalInode
out := (*fusekernel.NotifyInvalInodeOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyInvalInodeOut{}))))
out.Ino = uint64(o.Inode)
out.Off = o.Offset
out.Len = o.Length
case *fuseops.NotifyInvalEntry:
h.Error = fusekernel.NotifyCodeInvalEntry
out := (*fusekernel.NotifyInvalEntryOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyInvalEntryOut{}))))
out.Parent = uint64(o.Parent)
out.Namelen = uint32(len(o.Name))
m.AppendString(o.Name)
m.AppendString("\x00")
case *fuseops.NotifyDelete:
h.Error = fusekernel.NotifyCodeDelete
out := (*fusekernel.NotifyDeleteOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyDeleteOut{}))))
out.Parent = uint64(o.Parent)
out.Child = uint64(o.Child)
out.Namelen = uint32(len(o.Name))
m.AppendString(o.Name)
m.AppendString("\x00")
case *fuseops.NotifyStore:
h.Error = fusekernel.NotifyCodeStore
out := (*fusekernel.NotifyStoreOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyStoreOut{}))))
out.Nodeid = uint64(o.Inode)
out.Offset = o.Offset
out.Size = o.Length
m.Append(o.Data...)
m.ShrinkTo(buffer.OutMessageHeaderSize + int(unsafe.Sizeof(fusekernel.NotifyStoreOut{})) + int(o.Length))
case *fuseops.NotifyRetrieve:
h.Error = fusekernel.NotifyCodeRetrieve
out := (*fusekernel.NotifyRetrieveOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyRetrieveOut{}))))
out.Unique = o.Unique
out.Nodeid = uint64(o.Inode)
out.Offset = o.Offset
out.Size = o.Length
default:
panic(fmt.Sprintf("Unexpected notification: %#v", op))
}
return
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// General conversions // General conversions
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
func convertTime(t time.Time) (secs uint64, nsec uint32) {
totalNano := t.UnixNano()
secs = uint64(totalNano / 1e9)
nsec = uint32(totalNano % 1e9)
return secs, nsec
}
func convertAttributes(
inodeID fuseops.InodeID,
in *fuseops.InodeAttributes,
out *fusekernel.Attr) {
out.Ino = uint64(inodeID)
out.Size = in.Size
out.Atime, out.AtimeNsec = convertTime(in.Atime)
out.Mtime, out.MtimeNsec = convertTime(in.Mtime)
out.Ctime, out.CtimeNsec = convertTime(in.Ctime)
out.SetCrtime(convertTime(in.Crtime))
out.Nlink = in.Nlink
out.Uid = in.Uid
out.Gid = in.Gid
// round up to the nearest 512 boundary
out.Blocks = (in.Size + 512 - 1) / 512
// Set the mode.
out.Mode = uint32(in.Mode) & 0777
switch {
default:
out.Mode |= syscall.S_IFREG
case in.Mode&os.ModeDir != 0:
out.Mode |= syscall.S_IFDIR
case in.Mode&os.ModeDevice != 0:
if in.Mode&os.ModeCharDevice != 0 {
out.Mode |= syscall.S_IFCHR
} else {
out.Mode |= syscall.S_IFBLK
}
case in.Mode&os.ModeNamedPipe != 0:
out.Mode |= syscall.S_IFIFO
case in.Mode&os.ModeSymlink != 0:
out.Mode |= syscall.S_IFLNK
case in.Mode&os.ModeSocket != 0:
out.Mode |= syscall.S_IFSOCK
}
if in.Mode&os.ModeSetuid != 0 {
out.Mode |= syscall.S_ISUID
}
}
// Convert an absolute cache expiration time to a relative time from now for
// consumption by the fuse kernel module.
func convertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
// counts of nanoseconds (cf. http://goo.gl/EJupJV). So negative durations
// are right out. There is no need to cap the positive magnitude, because
// 2^64 seconds is well longer than the 2^63 ns range of time.Duration.
d := t.Sub(time.Now())
if d > 0 {
secs = uint64(d / time.Second)
nsecs = uint32((d % time.Second) / time.Nanosecond)
}
return secs, nsecs
}
func convertChildInodeEntry(
in *fuseops.ChildInodeEntry,
out *fusekernel.EntryOut) {
out.Nodeid = uint64(in.Child)
out.Generation = uint64(in.Generation)
out.EntryValid, out.EntryValidNsec = convertExpirationTime(in.EntryExpiration)
out.AttrValid, out.AttrValidNsec = convertExpirationTime(in.AttributesExpiration)
convertAttributes(in.Child, &in.Attributes, &out.Attr)
}
func convertFileMode(unixMode uint32) os.FileMode {
mode := os.FileMode(unixMode & 0777)
switch unixMode & syscall.S_IFMT {
case syscall.S_IFREG:
// nothing
case syscall.S_IFDIR:
mode |= os.ModeDir
case syscall.S_IFCHR:
mode |= os.ModeCharDevice | os.ModeDevice
case syscall.S_IFBLK:
mode |= os.ModeDevice
case syscall.S_IFIFO:
mode |= os.ModeNamedPipe
case syscall.S_IFLNK:
mode |= os.ModeSymlink
case syscall.S_IFSOCK:
mode |= os.ModeSocket
default:
// no idea
mode |= os.ModeDevice
}
if unixMode&syscall.S_ISUID != 0 {
mode |= os.ModeSetuid
}
if unixMode&syscall.S_ISGID != 0 {
mode |= os.ModeSetgid
}
return mode
}
func writeXattrSize(m *buffer.OutMessage, size uint32) { func writeXattrSize(m *buffer.OutMessage, size uint32) {
out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{})))) out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{}))))
out.Size = size out.Size = size

View File

@ -93,6 +93,11 @@ func describeRequest(op interface{}) (s string) {
addComponent("new_name %q", typed.NewName) addComponent("new_name %q", typed.NewName)
case *fuseops.ReadFileOp: case *fuseops.ReadFileOp:
addComponent("handle %d", typed.Handle)
addComponent("offset %d", typed.Offset)
addComponent("%d bytes", len(typed.Dst))
case *fuseops.VectoredReadOp:
addComponent("handle %d", typed.Handle) addComponent("handle %d", typed.Handle)
addComponent("offset %d", typed.Offset) addComponent("offset %d", typed.Offset)
addComponent("%d bytes", typed.Size) addComponent("%d bytes", typed.Size)
@ -115,9 +120,6 @@ func describeRequest(op interface{}) (s string) {
addComponent("offset %d", typed.Offset) addComponent("offset %d", typed.Offset)
addComponent("length %d", typed.Length) addComponent("length %d", typed.Length)
addComponent("mode %d", typed.Mode) addComponent("mode %d", typed.Mode)
case *fuseops.ReleaseFileHandleOp:
addComponent("handle %d", typed.Handle)
} }
// Use just the name if there is no extra info. // Use just the name if there is no extra info.
@ -144,10 +146,6 @@ func describeResponse(op interface{}) string {
addComponent("inode %v", entry.Child) addComponent("inode %v", entry.Child)
} }
} }
switch typed := op.(type) {
case *fuseops.OpenFileOp:
addComponent("handle %d", typed.Handle)
}
return fmt.Sprintf("%s (%s)", opName(op), strings.Join(components, ", ")) return fmt.Sprintf("%s", strings.Join(components, ", "))
} }

8
doc.go
View File

@ -16,15 +16,15 @@
// //
// The primary elements of interest are: // The primary elements of interest are:
// //
// - The fuseops package, which defines the operations that fuse might send // * The fuseops package, which defines the operations that fuse might send
// to your userspace daemon. // to your userspace daemon.
// //
// - The Server interface, which your daemon must implement. // * The Server interface, which your daemon must implement.
// //
// - fuseutil.NewFileSystemServer, which offers a convenient way to implement // * fuseutil.NewFileSystemServer, which offers a convenient way to implement
// the Server interface. // the Server interface.
// //
// - Mount, a function that allows for mounting a Server as a file system. // * Mount, a function that allows for mounting a Server as a file system.
// //
// Make sure to see the examples in the sub-packages of samples/, which double // Make sure to see the examples in the sub-packages of samples/, which double
// as tests for this package: http://godoc.org/github.com/jacobsa/fuse/samples // as tests for this package: http://godoc.org/github.com/jacobsa/fuse/samples

View File

@ -1,85 +0,0 @@
// Copyright 2023 Vitaliy Filippov
//
// 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 (
"time"
"syscall"
"github.com/jacobsa/fuse/internal/fusekernel"
)
////////////////////////////////////////////////////////////////////////
// General conversions
////////////////////////////////////////////////////////////////////////
func ConvertTime(t time.Time) (secs uint64, nsec uint32) {
totalNano := t.UnixNano()
secs = uint64(totalNano / 1e9)
nsec = uint32(totalNano % 1e9)
return secs, nsec
}
func ConvertAttributes(
inodeID InodeID,
in *InodeAttributes,
out *fusekernel.Attr) {
out.Ino = uint64(inodeID)
out.Size = in.Size
out.Atime, out.AtimeNsec = ConvertTime(in.Atime)
out.Mtime, out.MtimeNsec = ConvertTime(in.Mtime)
out.Ctime, out.CtimeNsec = ConvertTime(in.Ctime)
out.SetCrtime(ConvertTime(in.Crtime))
out.Nlink = in.Nlink
out.Uid = in.Uid
out.Gid = in.Gid
// round up to the nearest 512 boundary
out.Blocks = (in.Size + 512 - 1) / 512
// Set the mode.
out.Mode = ConvertGoMode(in.Mode)
if out.Mode & (syscall.S_IFCHR | syscall.S_IFBLK) != 0 {
out.Rdev = in.Rdev
}
}
// Convert an absolute cache expiration time to a relative time from now for
// consumption by the fuse kernel module.
func ConvertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
// counts of nanoseconds (https://tinyurl.com/4muvkr6k). So negative
// durations are right out. There is no need to cap the positive magnitude,
// because 2^64 seconds is well longer than the 2^63 ns range of
// time.Duration.
d := t.Sub(time.Now())
if d > 0 {
secs = uint64(d / time.Second)
nsecs = uint32((d % time.Second) / time.Nanosecond)
}
return secs, nsecs
}
func ConvertChildInodeEntry(
in *ChildInodeEntry,
out *fusekernel.EntryOut) {
out.Nodeid = uint64(in.Child)
out.Generation = uint64(in.Generation)
out.EntryValid, out.EntryValidNsec = ConvertExpirationTime(in.EntryExpiration)
out.AttrValid, out.AttrValidNsec = ConvertExpirationTime(in.AttributesExpiration)
ConvertAttributes(in.Child, &in.Attributes, &out.Attr)
}

View File

@ -1,74 +0,0 @@
package fuseops
import (
"os"
"syscall"
)
// ConvertFileMode returns an os.FileMode with the Go mode and permission bits
// set according to the Linux mode and permission bits.
func ConvertFileMode(unixMode uint32) os.FileMode {
mode := os.FileMode(unixMode & 0777)
switch unixMode & syscall.S_IFMT {
case syscall.S_IFREG:
// nothing
case syscall.S_IFDIR:
mode |= os.ModeDir
case syscall.S_IFCHR:
mode |= os.ModeCharDevice | os.ModeDevice
case syscall.S_IFBLK:
mode |= os.ModeDevice
case syscall.S_IFIFO:
mode |= os.ModeNamedPipe
case syscall.S_IFLNK:
mode |= os.ModeSymlink
case syscall.S_IFSOCK:
mode |= os.ModeSocket
default:
// no idea
}
if unixMode&syscall.S_ISUID != 0 {
mode |= os.ModeSetuid
}
if unixMode&syscall.S_ISGID != 0 {
mode |= os.ModeSetgid
}
if unixMode&syscall.S_ISVTX != 0 {
mode |= os.ModeSticky
}
return mode
}
// ConvertGoMode returns an integer with the Linux mode and permission bits
// set according to the Go mode and permission bits.
func ConvertGoMode(inMode os.FileMode) uint32 {
outMode := uint32(inMode) & 0777
switch {
default:
outMode |= syscall.S_IFREG
case inMode&os.ModeDir != 0:
outMode |= syscall.S_IFDIR
case inMode&os.ModeDevice != 0:
if inMode&os.ModeCharDevice != 0 {
outMode |= syscall.S_IFCHR
} else {
outMode |= syscall.S_IFBLK
}
case inMode&os.ModeNamedPipe != 0:
outMode |= syscall.S_IFIFO
case inMode&os.ModeSymlink != 0:
outMode |= syscall.S_IFLNK
case inMode&os.ModeSocket != 0:
outMode |= syscall.S_IFSOCK
}
if inMode&os.ModeSetuid != 0 {
outMode |= syscall.S_ISUID
}
if inMode&os.ModeSetgid != 0 {
outMode |= syscall.S_ISGID
}
if inMode&os.ModeSticky != 0 {
outMode |= syscall.S_ISVTX
}
return outMode
}

View File

@ -17,8 +17,6 @@ package fuseops
import ( import (
"os" "os"
"time" "time"
"github.com/jacobsa/fuse/internal/fusekernel"
) )
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -28,35 +26,25 @@ import (
// OpContext contains extra context that may be needed by some file systems. // OpContext contains extra context that may be needed by some file systems.
// See https://libfuse.github.io/doxygen/structfuse__context.html as a reference. // See https://libfuse.github.io/doxygen/structfuse__context.html as a reference.
type OpContext struct { type OpContext struct {
// FuseID is the Unique identifier for each operation from the kernel.
FuseID uint64
// PID of the process that is invoking the operation. // PID of the process that is invoking the operation.
// Not filled in case of a writepage operation. // Not filled in case of a writepage operation.
Pid uint32 Pid uint32
// UID of the process that is invoking the operation.
// Not filled in case of a writepage operation.
Uid uint32
// GID of the process that is invoking the operation.
// Not filled in case of a writepage operation.
Gid uint32
} }
// Return statistics about the file system's capacity and available resources. // Return statistics about the file system's capacity and available resources.
// //
// Called by statfs(2) and friends: // Called by statfs(2) and friends:
// //
// - (https://tinyurl.com/234ppacj) sys_statfs called user_statfs, which calls // * (https://goo.gl/Xi1lDr) sys_statfs called user_statfs, which calls
// vfs_statfs, which calls statfs_by_dentry. // vfs_statfs, which calls statfs_by_dentry.
// //
// - (https://tinyurl.com/u6keadjz) statfs_by_dentry calls the superblock // * (https://goo.gl/VAIOwU) statfs_by_dentry calls the superblock
// operation statfs, which in our case points at // operation statfs, which in our case points at
// fuse_statfs (https://tinyurl.com/mr45wd28) // fuse_statfs (cf. https://goo.gl/L7BTM3)
// //
// - (https://tinyurl.com/3wt3dw3c) fuse_statfs sends a statfs op, then uses // * (https://goo.gl/Zn7Sgl) fuse_statfs sends a statfs op, then uses
// convert_fuse_statfs to convert the response in a straightforward manner. // convert_fuse_statfs to convert the response in a straightforward
// manner.
// //
// This op is particularly important on OS X: if you don't implement it, the // This op is particularly important on OS X: if you don't implement it, the
// file system will not successfully mount. If you don't model a sane amount of // file system will not successfully mount. If you don't model a sane amount of
@ -67,15 +55,15 @@ type StatFSOp struct {
// system's capacity and space availability. // system's capacity and space availability.
// //
// On Linux this is surfaced as statfs::f_frsize, matching the posix standard // On Linux this is surfaced as statfs::f_frsize, matching the posix standard
// (https://tinyurl.com/2juj6ah6), which says that f_blocks and friends are // (http://goo.gl/LktgrF), which says that f_blocks and friends are in units
// in units of f_frsize. On OS X this is surfaced as statfs::f_bsize, which // of f_frsize. On OS X this is surfaced as statfs::f_bsize, which plays the
// plays the same roll. // same roll.
// //
// It appears as though the original intent of statvfs::f_frsize in the posix // It appears as though the original intent of statvfs::f_frsize in the posix
// standard was to support a smaller addressable unit than statvfs::f_bsize // standard was to support a smaller addressable unit than statvfs::f_bsize
// (cf. The Linux Programming Interface by Michael Kerrisk, // (cf. The Linux Programming Interface by Michael Kerrisk,
// https://tinyurl.com/5n8mjtws). Therefore users should probably arrange for // https://goo.gl/5LZMxQ). Therefore users should probably arrange for this
// this to be no larger than IoSize. // to be no larger than IoSize.
// //
// On Linux this can be any value, and will be faithfully returned to the // On Linux this can be any value, and will be faithfully returned to the
// caller of statfs(2) (see the code walk above). On OS X it appears that // caller of statfs(2) (see the code walk above). On OS X it appears that
@ -171,8 +159,6 @@ type SetInodeAttributesOp struct {
Handle *HandleID Handle *HandleID
// The attributes to modify, or nil for attributes that don't need a change. // The attributes to modify, or nil for attributes that don't need a change.
Uid *uint32
Gid *uint32
Size *uint64 Size *uint64
Mode *os.FileMode Mode *os.FileMode
Atime *time.Time Atime *time.Time
@ -193,22 +179,20 @@ type SetInodeAttributesOp struct {
// contain a note of this (but see also the note about the root inode below). // contain a note of this (but see also the note about the root inode below).
// For example, LookUpInodeOp and MkDirOp. The authoritative source is the // For example, LookUpInodeOp and MkDirOp. The authoritative source is the
// libfuse documentation, which states that any op that returns // libfuse documentation, which states that any op that returns
// fuse_reply_entry fuse_reply_create implicitly increments // fuse_reply_entry fuse_reply_create implicitly increments (cf.
// (https://tinyurl.com/2xd5zssm). // http://goo.gl/o5C7Dx).
// //
// If the reference count hits zero, the file system can forget about that ID // If the reference count hits zero, the file system can forget about that ID
// entirely, and even re-use it in future responses. The kernel guarantees that // entirely, and even re-use it in future responses. The kernel guarantees that
// it will not otherwise use it again. // it will not otherwise use it again.
// //
// The reference count corresponds to fuse_inode::nlookup // The reference count corresponds to fuse_inode::nlookup
// (https://tinyurl.com/ycka69ck). Some examples of where the kernel // (http://goo.gl/ut48S4). Some examples of where the kernel manipulates it:
// manipulates it:
// //
// - (https://tinyurl.com/s8dz2ays) Any caller to fuse_iget increases the // * (http://goo.gl/vPD9Oh) Any caller to fuse_iget increases the count.
// count. // * (http://goo.gl/B6tTTC) fuse_lookup_name calls fuse_iget.
// - (https://tinyurl.com/mu37ceua) fuse_lookup_name calls fuse_iget. // * (http://goo.gl/IlcxWv) fuse_create_open calls fuse_iget.
// - (https://tinyurl.com/2nyhhnsh) fuse_create_open calls fuse_iget. // * (http://goo.gl/VQMQul) fuse_dentry_revalidate increments after
// - (https://tinyurl.com/mnjpu3a9) fuse_dentry_revalidate increments after
// revalidating. // revalidating.
// //
// In contrast to all other inodes, RootInodeID begins with an implicit // In contrast to all other inodes, RootInodeID begins with an implicit
@ -216,13 +200,12 @@ type SetInodeAttributesOp struct {
// could be no such op, because the root cannot be referred to by name.) Code // could be no such op, because the root cannot be referred to by name.) Code
// walk: // walk:
// //
// - (https://tinyurl.com/yf8m2drx) fuse_fill_super calls // * (http://goo.gl/gWAheU) fuse_fill_super calls fuse_get_root_inode.
// fuse_get_root_inode.
// //
// - (https://tinyurl.com/35f86asu) fuse_get_root_inode calls fuse_iget // * (http://goo.gl/AoLsbb) fuse_get_root_inode calls fuse_iget without
// without sending any particular request. // sending any particular request.
// //
// - (https://tinyurl.com/s8dz2ays) fuse_iget increments nlookup. // * (http://goo.gl/vPD9Oh) fuse_iget increments nlookup.
// //
// File systems should tolerate but not rely on receiving forget ops for // File systems should tolerate but not rely on receiving forget ops for
// remaining inodes when the file system unmounts, including the root inode. // remaining inodes when the file system unmounts, including the root inode.
@ -237,32 +220,6 @@ type ForgetInodeOp struct {
OpContext OpContext OpContext OpContext
} }
// BatchForgetEntry represents one Inode entry to forget in the BatchForgetOp.
//
// Everything written in the ForgetInodeOp docs applies for the BatchForgetEntry
// too.
type BatchForgetEntry struct {
// The inode whose reference count should be decremented.
Inode InodeID
// The amount to decrement the reference count.
N uint64
}
// Decrement the reference counts for a list of inode IDs previously issued by the file
// system.
//
// This operation is a batch of ForgetInodeOp operations. Every entry in
// Entries is one ForgetInodeOp operation. See the docs of ForgetInodeOp
// for further details.
type BatchForgetOp struct {
// Entries is a list of Forget operations. One could treat every entry in the
// list as a single ForgetInodeOp operation.
Entries []BatchForgetEntry
OpContext OpContext
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Inode creation // Inode creation
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -272,11 +229,10 @@ type BatchForgetOp struct {
// //
// The Linux kernel appears to verify the name doesn't already exist (mkdir // The Linux kernel appears to verify the name doesn't already exist (mkdir
// calls mkdirat calls user_path_create calls filename_create, which verifies: // calls mkdirat calls user_path_create calls filename_create, which verifies:
// https://tinyurl.com/24yw46mf). Indeed, the tests in samples/memfs that call // http://goo.gl/FZpLu5). Indeed, the tests in samples/memfs that call in
// in parallel appear to bear this out. But osxfuse does not appear to // parallel appear to bear this out. But osxfuse does not appear to guarantee
// guarantee this (https://tinyurl.com/22587hcf). And if names may be created // this (cf. https://goo.gl/PqzZDv). And if names may be created outside of the
// outside of the kernel's control, it doesn't matter what the kernel does // kernel's control, it doesn't matter what the kernel does anyway.
// anyway.
// //
// 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 {
@ -297,14 +253,14 @@ type MkDirOp struct {
// Create a file inode as a child of an existing directory inode. The kernel // Create a file inode as a child of an existing directory inode. The kernel
// sends this in response to a mknod(2) call. It may also send it in special // sends this in response to a mknod(2) call. It may also send it in special
// cases such as an NFS export (https://tinyurl.com/5dwxr7c9). It is more typical // cases such as an NFS export (cf. https://goo.gl/HiLfnK). It is more typical
// to see CreateFileOp, which is received for an open(2) that creates a file. // to see CreateFileOp, which is received for an open(2) that creates a file.
// //
// The Linux kernel appears to verify the name doesn't already exist (mknod // The Linux kernel appears to verify the name doesn't already exist (mknod
// calls sys_mknodat calls user_path_create calls filename_create, which // calls sys_mknodat calls user_path_create calls filename_create, which
// verifies: https://tinyurl.com/24yw46mf). But osxfuse may not guarantee this, // verifies: http://goo.gl/FZpLu5). But osxfuse may not guarantee this, as with
// as with mkdir(2). And if names may be created outside of the kernel's // mkdir(2). And if names may be created outside of the kernel's control, it
// control, it doesn't matter what the kernel does anyway. // doesn't matter what the kernel does anyway.
// //
// 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 MkNodeOp struct { type MkNodeOp struct {
@ -315,9 +271,6 @@ type MkNodeOp struct {
Name string Name string
Mode os.FileMode Mode os.FileMode
// The device number (only valid if created file is a device)
Rdev uint32
// Set by the file system: information about the inode that was created. // Set by the file system: information about the inode that was created.
// //
// The lookup count for the inode is implicitly incremented. See notes on // The lookup count for the inode is implicitly incremented. See notes on
@ -330,10 +283,10 @@ type MkNodeOp struct {
// //
// 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
// flag and the kernel has observed that the file doesn't exist. (See for // flag and the kernel has observed that the file doesn't exist. (See for
// example lookup_open, https://tinyurl.com/49899mvb). However, osxfuse doesn't // example lookup_open, http://goo.gl/PlqE9d). However, osxfuse doesn't appear
// appear to make this check atomically (https://tinyurl.com/22587hcf). And if // to make this check atomically (cf. https://goo.gl/PqzZDv). And if names may
// names may be created outside of the kernel's control, it doesn't matter what // be created outside of the kernel's control, it doesn't matter what the
// the kernel does anyway. // kernel does anyway.
// //
// 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 {
@ -411,27 +364,27 @@ type CreateLinkOp struct {
// Rename a file or directory, given the IDs of the original parent directory // Rename a file or directory, given the IDs of the original parent directory
// and the new one (which may be the same). // and the new one (which may be the same).
// //
// In Linux, this is called by vfs_rename (https://tinyurl.com/2xbx9kr2), which // In Linux, this is called by vfs_rename (https://goo.gl/eERItT), which is
// is called by sys_renameat2 (https://tinyurl.com/4zyak2kt). // called by sys_renameat2 (https://goo.gl/fCC9qC).
// //
// The kernel takes care of ensuring that the source and destination are not // The kernel takes care of ensuring that the source and destination are not
// identical (in which case it does nothing), that the rename is not across // identical (in which case it does nothing), that the rename is not across
// file system boundaries, and that the destination doesn't already exist with // file system boundaries, and that the destination doesn't already exist with
// the wrong type. Some subtleties that the file system must care about: // the wrong type. Some subtleties that the file system must care about:
// //
// - If the new name is an existing directory, the file system must ensure it // * If the new name is an existing directory, the file system must ensure it
// is empty before replacing it, returning ENOTEMPTY otherwise. (This is // is empty before replacing it, returning ENOTEMPTY otherwise. (This is
// per the posix spec: https://tinyurl.com/5n865nx9) // per the posix spec: http://goo.gl/4XtT79)
// //
// - The rename must be atomic from the point of view of an observer of the // * The rename must be atomic from the point of view of an observer of the
// new name. That is, if the new name already exists, there must be no // new name. That is, if the new name already exists, there must be no
// point at which it doesn't exist. // point at which it doesn't exist.
// //
// - It is okay for the new name to be modified before the old name is // * It is okay for the new name to be modified before the old name is
// removed; these need not be atomic. In fact, the Linux man page // removed; these need not be atomic. In fact, the Linux man page
// explicitly says this is likely (https://tinyurl.com/mdpbpjmr). // explicitly says this is likely (cf. https://goo.gl/Y1wVZc).
// //
// - Linux bends over backwards (https://tinyurl.com/3hmt7puy) to ensure that // * Linux bends over backwards (https://goo.gl/pLDn3r) to ensure that
// neither the old nor the new parent can be concurrently modified. But // neither the old nor the new parent can be concurrently modified. But
// it's not clear whether OS X does this, and in any case it doesn't matter // it's not clear whether OS X does this, and in any case it doesn't matter
// for file systems that may be modified remotely. Therefore a careful file // for file systems that may be modified remotely. Therefore a careful file
@ -441,6 +394,7 @@ type CreateLinkOp struct {
// posix and the man pages are imprecise about the actual semantics of a // posix and the man pages are imprecise about the actual semantics of a
// rename if it's not atomic, so it is probably not disastrous to be loose // rename if it's not atomic, so it is probably not disastrous to be loose
// about this. // about this.
//
type RenameOp struct { type RenameOp struct {
// 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.
@ -460,7 +414,7 @@ type RenameOp struct {
// //
// The file system is responsible for checking that the directory is empty. // The file system is responsible for checking that the directory is empty.
// //
// Sample implementation in ext2: ext2_rmdir (https://tinyurl.com/bajkpcf9) // Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf)
type RmDirOp struct { type RmDirOp struct {
// 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.
@ -474,7 +428,7 @@ type RmDirOp struct {
// 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
// the file open. // the file open.
// //
// Sample implementation in ext2: ext2_unlink (https://tinyurl.com/3wpwedcp) // Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C)
type UnlinkOp struct { type UnlinkOp struct {
// 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.
@ -507,14 +461,6 @@ type OpenDirOp struct {
// a later call to ReleaseDirHandle. // a later call to ReleaseDirHandle.
Handle HandleID Handle HandleID
OpContext OpContext OpContext OpContext
// CacheDir conveys to the kernel to cache the response of next
// ReadDirOp as page cache. Once cached, listing on that directory will be
// served from the kernel until invalidated.
CacheDir bool
// KeepCache instructs the kernel to not invalidate the data cache on open calls.
KeepCache bool
} }
// Read entries from a directory previously opened with OpenDir. // Read entries from a directory previously opened with OpenDir.
@ -534,51 +480,47 @@ type ReadDirOp struct {
// at zero and is set by llseek and by the final consumed result returned by // at zero and is set by llseek and by the final consumed result returned by
// each call to ReadDir: // each call to ReadDir:
// //
// * (https://tinyurl.com/3ueykmaj) iterate_dir, which is called by // * (http://goo.gl/2nWJPL) iterate_dir, which is called by getdents(2) and
// getdents(2) and readdir(2), sets dir_context::pos to file::f_pos // readdir(2), sets dir_context::pos to file::f_pos before calling
// before calling f_op->iterate, and then does the opposite assignment // f_op->iterate, and then does the opposite assignment afterward.
// afterward.
// //
// * (https://tinyurl.com/a8urhfy9) fuse_readdir, which implements iterate // * (http://goo.gl/rTQVSL) fuse_readdir, which implements iterate for fuse
// for fuse directories, passes dir_context::pos as the offset to // directories, passes dir_context::pos as the offset to fuse_read_fill,
// fuse_read_fill, which passes it on to user-space. fuse_readdir later // which passes it on to user-space. fuse_readdir later calls
// calls parse_dirfile with the same context. // parse_dirfile with the same context.
// //
// * (https://tinyurl.com/5cev5fn4) For each returned result (except // * (http://goo.gl/vU5ukv) For each returned result (except perhaps the
// perhaps the last, which may be truncated by the page boundary), // last, which may be truncated by the page boundary), parse_dirfile
// parse_dirfile updates dir_context::pos with fuse_dirent::off. // updates dir_context::pos with fuse_dirent::off.
// //
// It is affected by the Posix directory stream interfaces in the following // It is affected by the Posix directory stream interfaces in the following
// manner: // manner:
// //
// * (https://tinyurl.com/2pjv5jvz, https://tinyurl.com/2r6h4mkj) opendir // * (http://goo.gl/fQhbyn, http://goo.gl/ns1kDF) opendir initially causes
// initially causes filepos to be set to zero. // filepos to be set to zero.
// //
// * (https://tinyurl.com/2yvcbcpv, https://tinyurl.com/bddezwp4) readdir // * (http://goo.gl/ezNKyR, http://goo.gl/xOmDv0) readdir allows the user
// allows the user to iterate through the directory one entry at a time. // to iterate through the directory one entry at a time. As each entry is
// As each entry is consumed, its d_off field is stored in // consumed, its d_off field is stored in __dirstream::filepos.
// __dirstream::filepos.
// //
// * (https://tinyurl.com/2pfbfe9v, https://tinyurl.com/4wtat58a) telldir // * (http://goo.gl/WEOXG8, http://goo.gl/rjSXl3) telldir allows the user
// allows the user to obtain the d_off field from the most recently // to obtain the d_off field from the most recently returned entry.
// returned entry.
// //
// * (https://tinyurl.com/bdynryef, https://tinyurl.com/4hysrnb8) seekdir // * (http://goo.gl/WG3nDZ, http://goo.gl/Lp0U6W) seekdir allows the user
// allows the user to seek backward to an offset previously returned by // to seek backward to an offset previously returned by telldir. It
// telldir. It stores the new offset in filepos, and calls llseek to // stores the new offset in filepos, and calls llseek to update the
// update the kernel's struct file. // kernel's struct file.
// //
// * (https://tinyurl.com/5n8dkb44, https://tinyurl.com/3jnn5nnn) rewinddir // * (http://goo.gl/gONQhz, http://goo.gl/VlrQkc) rewinddir allows the user
// allows the user to go back to the beginning of the directory, // to go back to the beginning of the directory, obtaining a fresh view.
// obtaining a fresh view. It updates filepos and calls llseek to update // It updates filepos and calls llseek to update the kernel's struct
// the kernel's struct file. // file.
// //
// Unfortunately, FUSE offers no way to intercept seeks // Unfortunately, FUSE offers no way to intercept seeks
// (https://tinyurl.com/4bm2sfjd), so there is no way to cause seekdir or // (http://goo.gl/H6gEXa), so there is no way to cause seekdir or rewinddir
// rewinddir to fail. Additionally, there is no way to distinguish an // to fail. Additionally, there is no way to distinguish an explicit
// explicit rewinddir followed by readdir from the initial readdir, or a // rewinddir followed by readdir from the initial readdir, or a rewinddir
// rewinddir from a seekdir to the value returned by telldir just after // from a seekdir to the value returned by telldir just after opendir.
// opendir.
// //
// Luckily, Posix is vague about what the user will see if they seek // Luckily, Posix is vague about what the user will see if they seek
// backwards, and requires the user not to seek to an old offset after a // backwards, and requires the user not to seek to an old offset after a
@ -588,18 +530,12 @@ type ReadDirOp struct {
// offset, and return array offsets into that cached listing. // offset, and return array offsets into that cached listing.
Offset DirOffset Offset DirOffset
// Whether this operation is a READDIRPLUS
//
// If true, then the FS must return inode attributes and expiration time
// along with each directory entry and increment its reference count.
Plus bool
// The destination buffer, whose length gives the size of the read. // The destination buffer, whose length gives the size of the read.
// //
// The output data should consist of a sequence of FUSE directory entries in // The output data should consist of a sequence of FUSE directory entries in
// the format generated by fuse_add_direntry (https://tinyurl.com/3r9t7d2p), // the format generated by fuse_add_direntry (http://goo.gl/qCcHCV), which is
// which is consumed by parse_dirfile (https://tinyurl.com/bevwty74). Use // consumed by parse_dirfile (http://goo.gl/2WUmD2). Use fuseutil.WriteDirent
// fuseutil.WriteDirent or fuseutil.WriteDirentPlus to generate this data. // to generate this data.
// //
// Each entry returned exposes a directory offset to the user that may later // Each entry returned exposes a directory offset to the user that may later
// show up in ReadDirRequest.Offset. See notes on that field for more // show up in ReadDirRequest.Offset. See notes on that field for more
@ -612,10 +548,10 @@ type ReadDirOp struct {
// entries available or the final entry would not fit. // entries available or the final entry would not fit.
// //
// Zero means that the end of the directory has been reached. This is // Zero means that the end of the directory has been reached. This is
// unambiguous because NAME_MAX (https://tinyurl.com/4r2b68jp) plus the size // unambiguous because NAME_MAX (https://goo.gl/ZxzKaE) plus the size of
// of fuse_dirent (https://tinyurl.com/mp43bu8) plus the 8-byte alignment of // fuse_dirent (https://goo.gl/WO8s3F) plus the 8-byte alignment of
// FUSE_DIRENT_ALIGN (https://tinyurl.com/3m3ewu7h) is less than the read // FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH) is less than the read size of
// size of PAGE_SIZE used by fuse_readdir (https://tinyurl.com/mrwxsfxw). // PAGE_SIZE used by fuse_readdir (cf. https://goo.gl/VajtS2).
BytesRead int BytesRead int
OpContext OpContext OpContext OpContext
} }
@ -627,8 +563,7 @@ type ReadDirOp struct {
// The kernel guarantees that the handle ID will not be used in further ops // The kernel guarantees that the handle ID will not be used in further ops
// sent to the file system (unless it is reissued by the file system). // sent to the file system (unless it is reissued by the file system).
// //
// Errors from this op are ignored by the kernel // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
// (https://tinyurl.com/2aaccyzk).
type ReleaseDirHandleOp struct { type ReleaseDirHandleOp struct {
// 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
@ -661,15 +596,15 @@ type OpenFileOp struct {
Handle HandleID Handle HandleID
// By default, fuse invalidates the kernel's page cache for an inode when a // By default, fuse invalidates the kernel's page cache for an inode when a
// new file handle is opened for that inode (https://tinyurl.com/yyb497zy). // new file handle is opened for that inode (cf. https://goo.gl/2rZ9uk). The
// The intent appears to be to allow users to "see" content that has changed // intent appears to be to allow users to "see" content that has changed
// remotely on a networked file system by re-opening the file. // remotely on a networked file system by re-opening the file.
// //
// For file systems where this is not a concern because all modifications for // For file systems where this is not a concern because all modifications for
// a particular inode go through the kernel, set this field to true to // a particular inode go through the kernel, set this field to true to
// disable this behavior. // disable this behavior.
// //
// (More discussion: https://tinyurl.com/4znxvzwh) // (More discussion: http://goo.gl/cafzWF)
// //
// Note that on OS X it appears that the behavior is always as if this field // Note that on OS X it appears that the behavior is always as if this field
// is set to true, regardless of its value, at least for files opened in the // is set to true, regardless of its value, at least for files opened in the
@ -685,8 +620,6 @@ type OpenFileOp struct {
// advance, for example, because contents are generated on the fly. // advance, for example, because contents are generated on the fly.
UseDirectIO bool UseDirectIO bool
OpenFlags fusekernel.OpenFlags
OpContext OpContext OpContext OpContext
} }
@ -704,35 +637,52 @@ type ReadFileOp struct {
// The offset within the file at which to read. // The offset within the file at which to read.
Offset int64 Offset int64
// The destination buffer, whose length gives the size of the read.
Dst []byte
// Set by the file system: the number of bytes read.
//
// The FUSE documentation requires that exactly the requested number of bytes
// be returned, except in the case of EOF or error (http://goo.gl/ZgfBkF).
// This appears to be because it uses file mmapping machinery
// (http://goo.gl/SGxnaN) to read a page at a time. It appears to understand
// where EOF is by checking the inode size (http://goo.gl/0BkqKD), returned
// by a previous call to LookUpInode, GetInodeAttributes, etc.
//
// If direct IO is enabled, semantics should match those of read(2).
BytesRead int
OpContext OpContext
}
// Vectored read - same as ReadFileOp, but the buffer isn't provided by the library.
// The file system returns a list of slices instead.
type VectoredReadOp struct {
// The file inode that we are reading, and the handle previously returned by
// CreateFile or OpenFile when opening that inode.
Inode InodeID
Handle HandleID
// The offset within the file at which to read.
Offset int64
// The size of the read. // The size of the read.
Size int64 Size int64
// The destination buffer, whose length gives the size of the read. // Set by the file system: data to send back to the client.
// For vectored reads, this field is always nil as the buffer is not provided.
Dst []byte
// Set by the file system:
// A list of slices of data to send back to the client for vectored reads.
Data [][]byte Data [][]byte
// Set by the file system: the number of bytes read. // Set by the file system: the number of bytes read.
// //
// The FUSE documentation requires that exactly the requested number of bytes // The FUSE documentation requires that exactly the requested number of bytes
// be returned, except in the case of EOF or error // be returned, except in the case of EOF or error (http://goo.gl/ZgfBkF).
// (https://tinyurl.com/2mzewn35). This appears to be because it uses file // This appears to be because it uses file mmapping machinery
// mmapping machinery (https://tinyurl.com/avxy3dvm) to read a page at a // (http://goo.gl/SGxnaN) to read a page at a time. It appears to understand
// time. It appears to understand where EOF is by checking the inode size // where EOF is by checking the inode size (http://goo.gl/0BkqKD), returned
// (https://tinyurl.com/2eteerzt), returned by a previous call to // by a previous call to LookUpInode, GetInodeAttributes, etc.
// LookUpInode, GetInodeAttributes, etc.
// //
// If direct IO is enabled, semantics should match those of read(2). // If direct IO is enabled, semantics should match those of read(2).
BytesRead int BytesRead int
OpContext OpContext OpContext OpContext
// If set, this function will be invoked after the operation response has been
// sent to the kernel and before the buffers containing the response data are
// freed.
Callback func()
} }
// Write data to a file previously opened with CreateFile or OpenFile. // Write data to a file previously opened with CreateFile or OpenFile.
@ -741,23 +691,22 @@ type ReadFileOp struct {
// cache and the page is marked dirty. Later the kernel may write back the // cache and the page is marked dirty. Later the kernel may write back the
// page via the FUSE VFS layer, causing this op to be sent: // page via the FUSE VFS layer, causing this op to be sent:
// //
// - The kernel calls address_space_operations::writepage when a dirty page // * The kernel calls address_space_operations::writepage when a dirty page
// needs to be written to backing store (https://tinyurl.com/yck2sf5u). // needs to be written to backing store (cf. http://goo.gl/Ezbewg). Fuse
// Fuse sets this to fuse_writepage (https://tinyurl.com/5n989f8p). // sets this to fuse_writepage (cf. http://goo.gl/IeNvLT).
// //
// - (https://tinyurl.com/mvn6zv3j) fuse_writepage calls // * (http://goo.gl/Eestuy) fuse_writepage calls fuse_writepage_locked.
// fuse_writepage_locked.
// //
// - (https://tinyurl.com/2wn8scwb) fuse_writepage_locked makes a write // * (http://goo.gl/RqYIxY) fuse_writepage_locked makes a write request to
// request to the userspace server. // the userspace server.
// //
// Note that the kernel *will* ensure that writes are received and acknowledged // Note that the kernel *will* ensure that writes are received and acknowledged
// by the file system before sending a FlushFileOp when closing the file // by the file system before sending a FlushFileOp when closing the file
// descriptor to which they were written. Cf. the notes on // descriptor to which they were written. Cf. the notes on
// fuse.MountConfig.DisableWritebackCaching. // fuse.MountConfig.DisableWritebackCaching.
// //
// (See also https://tinyurl.com/5dchkdtx, fuse-devel thread "Fuse guarantees // (See also http://goo.gl/ocdTdM, fuse-devel thread "Fuse guarantees on
// on concurrent requests".) // concurrent requests".)
type WriteFileOp struct { type WriteFileOp struct {
// 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.
@ -789,41 +738,25 @@ type WriteFileOp struct {
// The data to write. // The data to write.
// //
// The FUSE documentation requires that exactly the number of bytes supplied // The FUSE documentation requires that exactly the number of bytes supplied
// be written, except on error (https://tinyurl.com/yuruk5tx). This appears // be written, except on error (http://goo.gl/KUpwwn). This appears to be
// to be because it uses file mmapping machinery // because it uses file mmapping machinery (http://goo.gl/SGxnaN) to write a
// (https://tinyurl.com/avxy3dvm) to write a page at a time. // page at a time.
Data []byte Data []byte
// Set by the file system: "no reuse" flag.
//
// By default, the Data buffer is reused by the library, so the file system
// must copy the data if it wants to use it later.
//
// However, if the file system sets this flag to true, the library doesn't
// reuse this buffer, so the file system can safely store and use Data slice
// without copying memory.
SuppressReuse bool
OpContext OpContext OpContext OpContext
// If set, this function will be invoked after the operation response has been
// sent to the kernel and before the buffers containing the response data are
// freed.
Callback func()
} }
// 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
// (https://tinyurl.com/y2kdrfzw). Code walk for that case: // (cf. http://goo.gl/j9X8nB). Code walk for that case:
// //
// - (https://tinyurl.com/2s44cefz) sys_fsync calls do_fsync, calls // * (http://goo.gl/IQkWZa) sys_fsync calls do_fsync, calls vfs_fsync, calls
// vfs_fsync, calls vfs_fsync_range. // vfs_fsync_range.
// //
// - (https://tinyurl.com/bdhhfam5) vfs_fsync_range calls f_op->fsync. // * (http://goo.gl/5L2SMy) vfs_fsync_range calls f_op->fsync.
// //
// Note that this is also sent by fdatasync(2) (https://tinyurl.com/ja5wtszf), // Note that this is also sent by fdatasync(2) (cf. http://goo.gl/01R7rF), and
// and may be sent for msync(2) with the MS_SYNC flag (see the notes on // may be sent for msync(2) with the MS_SYNC flag (see the notes on
// FlushFileOp). // FlushFileOp).
// //
// See also: FlushFileOp, which may perform a similar function when closing a // See also: FlushFileOp, which may perform a similar function when closing a
@ -838,42 +771,39 @@ type SyncFileOp struct {
// 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.
// //
// vfs.txt documents this as being sent for each close(2) system call // vfs.txt documents this as being sent for each close(2) system call (cf.
// (https://tinyurl.com/r4ujfxkc). Code walk for that case: // http://goo.gl/FSkbrq). Code walk for that case:
// //
// - (https://tinyurl.com/2kzyyjcu) sys_close calls __close_fd, calls // * (http://goo.gl/e3lv0e) sys_close calls __close_fd, calls filp_close.
// filp_close. // * (http://goo.gl/nI8fxD) filp_close calls f_op->flush (fuse_flush).
// - (https://tinyurl.com/4zdxrz52) filp_close calls f_op->flush
// (fuse_flush).
// //
// But note that this is also sent in other contexts where a file descriptor is // But note that this is also sent in other contexts where a file descriptor is
// closed, such as dup2(2) (https://tinyurl.com/5bj3z3f5). In the case of // closed, such as dup2(2) (cf. http://goo.gl/NQDvFS). In the case of close(2),
// close(2), a flush error is returned to the user. For dup2(2), it is not. // a flush error is returned to the user. For dup2(2), it is not.
// //
// One potentially significant case where this may not be sent is mmap'd files, // One potentially significant case where this may not be sent is mmap'd files,
// where the behavior is complicated: // where the behavior is complicated:
// //
// - munmap(2) does not cause flushes (https://tinyurl.com/ycy9z2jb). // * munmap(2) does not cause flushes (cf. http://goo.gl/j8B9g0).
// //
// - On OS X, if a user modifies a mapped file via the mapping before closing // * On OS X, if a user modifies a mapped file via the mapping before
// the file with close(2), the WriteFileOps for the modifications may not // closing the file with close(2), the WriteFileOps for the modifications
// be received before the FlushFileOp for the close(2) (cf. // may not be received before the FlushFileOp for the close(2) (cf.
// https://github.com/osxfuse/osxfuse/issues/202). It appears that this may // https://github.com/osxfuse/osxfuse/issues/202). It appears that this may
// be fixed in osxfuse 3 (https://tinyurl.com/2ne2jv8u). // be fixed in osxfuse 3 (cf. https://goo.gl/rtvbko).
// //
// - However, you safely can arrange for writes via a mapping to be flushed // * However, you safely can arrange for writes via a mapping to be
// by calling msync(2) followed by close(2). On OS X msync(2) will cause a // flushed by calling msync(2) followed by close(2). On OS X msync(2)
// WriteFileOps to go through and close(2) will cause a FlushFile as usual // will cause a WriteFileOps to go through and close(2) will cause a
// (https://tinyurl.com/2p9b4axf). On Linux, msync(2) does nothing unless // FlushFile as usual (cf. http://goo.gl/kVmNcx). On Linux, msync(2) does
// you set the MS_SYNC flag, in which case it causes a SyncFileOp to be // nothing unless you set the MS_SYNC flag, in which case it causes a
// sent (https://tinyurl.com/2y3d9hhj). // SyncFileOp to be sent (cf. http://goo.gl/P3mErk).
// //
// In summary: if you make data durable in both FlushFile and SyncFile, then // In summary: if you make data durable in both FlushFile and SyncFile, then
// your users can get safe behavior from mapped files on both operating systems // your users can get safe behavior from mapped files on both operating systems
// by calling msync(2) with MS_SYNC, followed by munmap(2), followed by // by calling msync(2) with MS_SYNC, followed by munmap(2), followed by
// close(2). On Linux, the msync(2) is optional (cf. // close(2). On Linux, the msync(2) is optional (cf. http://goo.gl/EIhAxv and
// https://tinyurl.com/unesszdp and the notes on WriteFileOp). // the notes on WriteFileOp).
// //
// Because of cases like dup2(2), FlushFileOps are not necessarily one to one // Because of cases like dup2(2), FlushFileOps are not necessarily one to one
// with OpenFileOps. They should not be used for reference counting, and the // with OpenFileOps. They should not be used for reference counting, and the
@ -900,8 +830,7 @@ type FlushFileOp struct {
// The kernel guarantees that the handle ID will not be used in further calls // The kernel guarantees that the handle ID will not be used in further calls
// to the file system (unless it is reissued by the file system). // to the file system (unless it is reissued by the file system).
// //
// Errors from this op are ignored by the kernel // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
// (https://tinyurl.com/2aaccyzk).
type ReleaseFileHandleOp struct { type ReleaseFileHandleOp struct {
// 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
@ -1025,132 +954,3 @@ type FallocateOp struct {
Mode uint32 Mode uint32
OpContext OpContext OpContext OpContext
} }
type SyncFSOp struct {
Inode InodeID
OpContext OpContext
}
// Request notifications when the file system user calls poll/select or
// similar operations on a file.
type PollOp struct {
// The inode and handle the user wants to poll
Inode InodeID
Handle HandleID
// Kh is the "kernel handle". The reason behind it is that it's allocated
// by the kernel on file allocation and guaranteed to be unique as opposed
// to regular file handles (HandleID) generated by the userland server
// (by us). Kh has to be used in NotifyPollWakeupOut replies.
Kh uint64
// Poll flags
Flags fusekernel.PollFlags
// Requested events
Events fusekernel.PollEvents
// Set by the file system: the actual events that have happened
// since the last poll
Revents fusekernel.PollEvents
OpContext OpContext
}
// Notify consumers waiting for poll/epoll that events are incoming
// for the specified kernel handle. The kernel will send a PollOp request
// to get the event mask after receiving this notification
type NotifyPollWakeup struct {
Kh uint64
}
// Notify to invalidate cache for an inode.
//
// If the filesystem has writeback caching enabled, invalidating an inode
// will first trigger a writeback of all dirty pages. The call will block
// until all writeback requests have completed and the inode has been
// invalidated. It will, however, not wait for completion of pending writeback
// requests that have been issued before.
type NotifyInvalInode struct {
Inode InodeID
Offset int64
Length int64
}
// Notify to invalidate parent attributes and the dentry matching parent/name
//
// To avoid a deadlock this request must not be sent in the execution path
// of a related filesytem operation or within any code that could hold a lock
// that could be needed to execute such an operation. As of kernel 4.18, a
// "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(),
// rename(), link() or create() request for the parent, and a setattr(),
// unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or
// readdirplus() request for the inode itself.
//
// When called correctly, it will never block.
type NotifyInvalEntry struct {
Parent InodeID
Name string
}
// This request behaves like NotifyInvalEntry with the following additional
// effect (at least as of Linux kernel 4.8):
//
// If the provided child inode matches the inode that is currently associated
// with the cached dentry, and if there are any inotify watches registered for
// the dentry, then the watchers are informed that the dentry has been deleted.
//
// To avoid a deadlock this request must not be sent while executing a
// related filesytem operation or while holding a lock that could be needed to
// execute such an operation.
type NotifyDelete struct {
Parent InodeID
Child InodeID
Name string
}
// Store data to the kernel buffers
//
// Synchronously store data in the kernel buffers belonging to the given inode.
// The stored data is marked up-to-date (no read will be performed against it,
// unless it's invalidated or evicted from the cache).
//
// If the stored data overflows the current file size, then the size is extended,
// similarly to a write(2) on the filesystem.
//
// If this request returns an error, then the store wasn't fully completed, but
// it may have been partially completed.
type NotifyStore struct {
Inode InodeID
Offset uint64
Length uint32
Data [][]byte
}
// Retrieve data from the kernel buffers belonging to the given inode
//
// If successful then the kernel will send a NotifyRetrieveReplyOp as a reply.
// Only present pages are returned in the retrieve reply. Retrieving stops when it
// finds a non-present page and only data prior to that is returned.
//
// If this request returns an error, then the retrieve will not be completed and
// no reply will be sent.
//
// This request doesn't change the dirty state of pages in the kernel buffer. For
// dirty pages the write() method will be called regardless of having been retrieved
// previously.
type NotifyRetrieve struct {
Inode InodeID
Unique uint64
Offset uint64
Length uint32
}
// Matches the size of WriteIn
type NotifyRetrieveReplyOp struct {
Inode InodeID
Unique uint64
Offset uint64
Length uint32
OpContext OpContext
}

View File

@ -27,7 +27,7 @@ import (
// RootInodeID. // RootInodeID.
// //
// This corresponds to struct inode::i_no in the VFS layer. // This corresponds to struct inode::i_no in the VFS layer.
// (https://tinyurl.com/23sr9svd) // (Cf. http://goo.gl/tvYyQt)
type InodeID uint64 type InodeID uint64
// RootInodeID is a distinguished inode ID that identifies the root of the file // RootInodeID is a distinguished inode ID that identifies the root of the file
@ -57,7 +57,7 @@ func init() {
} }
// InodeAttributes contains attributes for a file or directory inode. It // InodeAttributes contains attributes for a file or directory inode. It
// corresponds to struct inode (https://tinyurl.com/23sr9svd). // corresponds to struct inode (cf. http://goo.gl/tvYyQt).
type InodeAttributes struct { type InodeAttributes struct {
Size uint64 Size uint64
@ -70,26 +70,23 @@ type InodeAttributes struct {
// Note that in contrast to the defaults for FUSE, this package mounts file // Note that in contrast to the defaults for FUSE, this package mounts file
// systems in a manner such that the kernel checks inode permissions in the // systems in a manner such that the kernel checks inode permissions in the
// standard posix way. This is implemented by setting the default_permissions // standard posix way. This is implemented by setting the default_permissions
// mount option (https://tinyurl.com/ytun2zsn, https://tinyurl.com/52hz9vya). // mount option (cf. http://goo.gl/1LxOop and http://goo.gl/1pTjuk).
// //
// For example, in the case of mkdir: // For example, in the case of mkdir:
// //
// * (https://tinyurl.com/4yp9bu3h) sys_mkdirat calls inode_permission. // * (http://goo.gl/JkdxDI) sys_mkdirat calls inode_permission.
// //
// * (...) inode_permission eventually calls do_inode_permission. // * (...) inode_permission eventually calls do_inode_permission.
// //
// * (https://tinyurl.com/5f9k2eya) calls i_op->permission, which is // * (http://goo.gl/aGCsmZ) calls i_op->permission, which is
// fuse_permission (https://tinyurl.com/4kevbw27). // fuse_permission (cf. http://goo.gl/VZ9beH).
// //
// * (https://tinyurl.com/nfea3pwj) fuse_permission doesn't do anything at // * (http://goo.gl/5kqUKO) fuse_permission doesn't do anything at all for
// all for several code paths if FUSE_DEFAULT_PERMISSIONS is unset. In // several code paths if FUSE_DEFAULT_PERMISSIONS is unset. In contrast,
// contrast, if that flag *is* set, then it calls generic_permission. // if that flag *is* set, then it calls generic_permission.
// //
Mode os.FileMode Mode os.FileMode
// The device number. Only valid if the file is a device
Rdev uint32
// Time information. See `man 2 stat` for full details. // Time information. See `man 2 stat` for full details.
Atime time.Time // Time of last access Atime time.Time // Time of last access
Mtime time.Time // Time of last modification Mtime time.Time // Time of last modification
@ -117,15 +114,16 @@ func (a *InodeAttributes) DebugString() string {
// when an ID is reused. // when an ID is reused.
// //
// This corresponds to struct inode::i_generation in the VFS layer. // This corresponds to struct inode::i_generation in the VFS layer.
// (https://tinyurl.com/23sr9svd) // (Cf. http://goo.gl/tvYyQt)
// //
// Some related reading: // Some related reading:
// //
// http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html // http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html
// http://stackoverflow.com/q/11071996/1505451 // http://stackoverflow.com/q/11071996/1505451
// https://tinyurl.com/yn7wmcmy // http://goo.gl/CqvwyX
// http://julipedia.meroh.net/2005/09/nfs-file-handles.html // http://julipedia.meroh.net/2005/09/nfs-file-handles.html
// https://tinyurl.com/2c8vsfrs // http://goo.gl/wvo3MB
//
type GenerationNumber uint64 type GenerationNumber uint64
// HandleID is an opaque 64-bit number used to identify a particular open // HandleID is an opaque 64-bit number used to identify a particular open
@ -160,7 +158,7 @@ type ChildInodeEntry struct {
// Ownership information in particular must be set to something reasonable or // Ownership information in particular must be set to something reasonable or
// by default root will own everything and unprivileged users won't be able // by default root will own everything and unprivileged users won't be able
// to do anything useful. In traditional file systems in the kernel, the // to do anything useful. In traditional file systems in the kernel, the
// function inode_init_owner (https://tinyurl.com/5yfdrfdf) contains the // function inode_init_owner (http://goo.gl/5qavg8) contains the
// standards-compliant logic for this. // standards-compliant logic for this.
Attributes InodeAttributes Attributes InodeAttributes
@ -169,19 +167,16 @@ type ChildInodeEntry struct {
// //
// For example, this is the abridged call chain for fstat(2): // For example, this is the abridged call chain for fstat(2):
// //
// * (https://tinyurl.com/bdd6ek3c) fstat calls vfs_fstat. // * (http://goo.gl/tKBH1p) fstat calls vfs_fstat.
// * (https://tinyurl.com/3enne935) vfs_fstat eventuall calls // * (http://goo.gl/3HeITq) vfs_fstat eventuall calls vfs_getattr_nosec.
// vfs_getattr_nosec. // * (http://goo.gl/DccFQr) vfs_getattr_nosec calls i_op->getattr.
// * (https://tinyurl.com/y5rkhzx4) vfs_getattr_nosec calls i_op->getattr. // * (http://goo.gl/dpKkst) fuse_getattr calls fuse_update_attributes.
// * (https://tinyurl.com/33hawubc) fuse_getattr calls // * (http://goo.gl/yNlqPw) fuse_update_attributes uses the values in the
// fuse_update_attributes. // struct inode if allowed, otherwise calling out to the user-space code.
// * (https://tinyurl.com/ywhhshxt) fuse_update_attributes uses the values
// in the struct inode if allowed, otherwise calling out to the
// user-space code.
// //
// In addition to obvious cases like fstat, this is also used in more subtle // In addition to obvious cases like fstat, this is also used in more subtle
// cases like updating size information before seeking // cases like updating size information before seeking (http://goo.gl/2nnMFa)
// (https://tinyurl.com/hv2jabnh) or reading (https://tinyurl.com/bdkpz96v). // or reading (http://goo.gl/FQSWs8).
// //
// Most 'real' file systems do not set inode_operations::getattr, and // Most 'real' file systems do not set inode_operations::getattr, and
// therefore vfs_getattr_nosec calls generic_fillattr which simply grabs the // therefore vfs_getattr_nosec calls generic_fillattr which simply grabs the
@ -208,18 +203,16 @@ type ChildInodeEntry struct {
// As in the discussion of attribute caching above, unlike real file systems, // As in the discussion of attribute caching above, unlike real file systems,
// FUSE file systems may spontaneously change their name -> inode mapping. // FUSE file systems may spontaneously change their name -> inode mapping.
// Therefore the FUSE VFS layer uses dentry_operations::d_revalidate // Therefore the FUSE VFS layer uses dentry_operations::d_revalidate
// (https://tinyurl.com/ydb8ncrk) to intercept lookups and revalidate by // (http://goo.gl/dVea0h) to intercept lookups and revalidate by calling the
// calling the user-space LookUpInode method. However the latter may be slow, // user-space LookUpInode method. However the latter may be slow, so it
// so it caches the entries until the time defined by this field. // caches the entries until the time defined by this field.
// //
// Example code walk: // Example code walk:
// //
// * (https://tinyurl.com/crddueft) lookup_dcache calls d_revalidate if // * (http://goo.gl/M2G3tO) lookup_dcache calls d_revalidate if enabled.
// enabled. // * (http://goo.gl/ef0Elu) fuse_dentry_revalidate just uses the dentry's
// // inode if fuse_dentry_time(entry) hasn't passed. Otherwise it sends a
// * (https://tinyurl.com/bdsxacjy) fuse_dentry_revalidate just uses the // lookup request.
// dentry's inode if fuse_dentry_time(entry) hasn't passed. Otherwise
// it sends a lookup request.
// //
// Leave at the zero value to disable caching. // Leave at the zero value to disable caching.
// //

View File

@ -30,7 +30,7 @@ func (f sortedEntries) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
// Read the directory with the given name and return a list of directory // Read the directory with the given name and return a list of directory
// entries, sorted by name. // entries, sorted by name.
// //
// Unlike ioutil.ReadDir (https://tinyurl.com/yft8kkxb), this function does not // Unlike ioutil.ReadDir (cf. http://goo.gl/i0nNP4), this function does not
// silently ignore "file not found" errors when stat'ing the names read from // silently ignore "file not found" errors when stat'ing the names read from
// the directory. // the directory.
func ReadDirPicky(dirname string) (entries []os.FileInfo, err error) { func ReadDirPicky(dirname string) (entries []os.FileInfo, err error) {

View File

@ -28,7 +28,7 @@ func extractBirthtime(sys interface{}) (birthtime time.Time, ok bool) {
} }
func extractNlink(sys interface{}) (nlink uint64, ok bool) { func extractNlink(sys interface{}) (nlink uint64, ok bool) {
return uint64(sys.(*syscall.Stat_t).Nlink), true return sys.(*syscall.Stat_t).Nlink, true
} }
func getTimes(stat *syscall.Stat_t) (atime, ctime, mtime time.Time) { func getTimes(stat *syscall.Stat_t) (atime, ctime, mtime time.Time) {

View File

@ -19,7 +19,6 @@ import (
"unsafe" "unsafe"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/internal/fusekernel"
) )
type DirentType uint32 type DirentType uint32
@ -51,22 +50,14 @@ type Dirent struct {
Type DirentType Type DirentType
} }
// Write the supplied directory entry into the given buffer in the format // Write the supplied directory entry intto the given buffer in the format
// expected in fuseops.ReadDirOp.Data, returning the number of bytes written. // expected in fuseops.ReadFileOp.Data, returning the number of bytes written.
// Return zero if the entry would not fit. // Return zero if the entry would not fit.
func WriteDirent(buf []byte, d Dirent) (n int) { func WriteDirent(buf []byte, d Dirent) (n int) {
return WriteDirentPlus(buf, nil, d)
}
// Write the supplied directory entry and, optionally, inode entry into the
// given buffer in the format expected in fuseops.ReadDirOp.Data with enabled
// READDIRPLUS capability, returning the number of bytes written.
// Returns zero if the entry would not fit.
func WriteDirentPlus(buf []byte, e *fuseops.ChildInodeEntry, d Dirent) (n int) {
// We want to write bytes with the layout of fuse_dirent // We want to write bytes with the layout of fuse_dirent
// (https://tinyurl.com/4k7y2h9r) in host order. The struct must be aligned // (http://goo.gl/BmFxob) in host order. The struct must be aligned according
// according to FUSE_DIRENT_ALIGN (https://tinyurl.com/3m3ewu7h), which // to FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH), which dictates 8-byte
// dictates 8-byte alignment. // alignment.
type fuse_dirent struct { type fuse_dirent struct {
ino uint64 ino uint64
off uint64 off uint64
@ -87,21 +78,10 @@ func WriteDirentPlus(buf []byte, e *fuseops.ChildInodeEntry, d Dirent) (n int) {
// Do we have enough room? // Do we have enough room?
totalLen := direntSize + len(d.Name) + padLen totalLen := direntSize + len(d.Name) + padLen
if e != nil {
// READDIRPLUS was added in protocol 7.21, entry attributes were added in 7.9
// So here EntryOut is always full-length
totalLen += int(unsafe.Sizeof(fusekernel.EntryOut{}))
}
if totalLen > len(buf) { if totalLen > len(buf) {
return n return n
} }
if e != nil {
out := (*fusekernel.EntryOut)(unsafe.Pointer(&buf[n]))
fuseops.ConvertChildInodeEntry(e, out)
n += int(unsafe.Sizeof(fusekernel.EntryOut{}))
}
// Write the header. // Write the header.
de := fuse_dirent{ de := fuse_dirent{
ino: uint64(d.Inode), ino: uint64(d.Inode),

View File

@ -39,7 +39,6 @@ type FileSystem interface {
GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error
SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
ForgetInode(context.Context, *fuseops.ForgetInodeOp) error ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
BatchForget(context.Context, *fuseops.BatchForgetOp) error
MkDir(context.Context, *fuseops.MkDirOp) error MkDir(context.Context, *fuseops.MkDirOp) error
MkNode(context.Context, *fuseops.MkNodeOp) error MkNode(context.Context, *fuseops.MkNodeOp) error
CreateFile(context.Context, *fuseops.CreateFileOp) error CreateFile(context.Context, *fuseops.CreateFileOp) error
@ -53,6 +52,7 @@ type FileSystem interface {
ReleaseDirHandle(context.Context, *fuseops.ReleaseDirHandleOp) error ReleaseDirHandle(context.Context, *fuseops.ReleaseDirHandleOp) error
OpenFile(context.Context, *fuseops.OpenFileOp) error OpenFile(context.Context, *fuseops.OpenFileOp) error
ReadFile(context.Context, *fuseops.ReadFileOp) error ReadFile(context.Context, *fuseops.ReadFileOp) error
VectoredRead(context.Context, *fuseops.VectoredReadOp) error
WriteFile(context.Context, *fuseops.WriteFileOp) error WriteFile(context.Context, *fuseops.WriteFileOp) error
SyncFile(context.Context, *fuseops.SyncFileOp) error SyncFile(context.Context, *fuseops.SyncFileOp) error
FlushFile(context.Context, *fuseops.FlushFileOp) error FlushFile(context.Context, *fuseops.FlushFileOp) error
@ -63,10 +63,6 @@ type FileSystem interface {
ListXattr(context.Context, *fuseops.ListXattrOp) error ListXattr(context.Context, *fuseops.ListXattrOp) error
SetXattr(context.Context, *fuseops.SetXattrOp) error SetXattr(context.Context, *fuseops.SetXattrOp) error
Fallocate(context.Context, *fuseops.FallocateOp) error Fallocate(context.Context, *fuseops.FallocateOp) error
SyncFS(context.Context, *fuseops.SyncFSOp) error
Poll(context.Context, *fuseops.PollOp) error
SetConnection(*fuse.Connection)
// 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
@ -85,8 +81,8 @@ type FileSystem interface {
// //
// (It is safe to naively process ops concurrently because the kernel // (It is safe to naively process ops concurrently because the kernel
// guarantees to serialize operations that the user expects to happen in order, // guarantees to serialize operations that the user expects to happen in order,
// cf. https://tinyurl.com/bddm85v5, fuse-devel thread "Fuse guarantees on // cf. http://goo.gl/jnkHPO, fuse-devel thread "Fuse guarantees on concurrent
// concurrent requests"). // requests").
func NewFileSystemServer(fs FileSystem) fuse.Server { func NewFileSystemServer(fs FileSystem) fuse.Server {
return &fileSystemServer{ return &fileSystemServer{
fs: fs, fs: fs,
@ -99,8 +95,6 @@ type fileSystemServer struct {
} }
func (s *fileSystemServer) ServeOps(c *fuse.Connection) { func (s *fileSystemServer) ServeOps(c *fuse.Connection) {
s.fs.SetConnection(c)
// When we are done, we clean up by waiting for all in-flight ops then // When we are done, we clean up by waiting for all in-flight ops then
// destroying the file system. // destroying the file system.
defer func() { defer func() {
@ -158,22 +152,6 @@ func (s *fileSystemServer) handleOp(
case *fuseops.ForgetInodeOp: case *fuseops.ForgetInodeOp:
err = s.fs.ForgetInode(ctx, typed) err = s.fs.ForgetInode(ctx, typed)
case *fuseops.BatchForgetOp:
err = s.fs.BatchForget(ctx, typed)
if err == fuse.ENOSYS {
// Handle as a series of single-inode forget operations
for _, entry := range typed.Entries {
err = s.fs.ForgetInode(ctx, &fuseops.ForgetInodeOp{
Inode: entry.Inode,
N: entry.N,
OpContext: typed.OpContext,
})
if err != nil {
break
}
}
}
case *fuseops.MkDirOp: case *fuseops.MkDirOp:
err = s.fs.MkDir(ctx, typed) err = s.fs.MkDir(ctx, typed)
@ -213,6 +191,9 @@ func (s *fileSystemServer) handleOp(
case *fuseops.ReadFileOp: case *fuseops.ReadFileOp:
err = s.fs.ReadFile(ctx, typed) err = s.fs.ReadFile(ctx, typed)
case *fuseops.VectoredReadOp:
err = s.fs.VectoredRead(ctx, typed)
case *fuseops.WriteFileOp: case *fuseops.WriteFileOp:
err = s.fs.WriteFile(ctx, typed) err = s.fs.WriteFile(ctx, typed)
@ -242,12 +223,6 @@ func (s *fileSystemServer) handleOp(
case *fuseops.FallocateOp: case *fuseops.FallocateOp:
err = s.fs.Fallocate(ctx, typed) err = s.fs.Fallocate(ctx, typed)
case *fuseops.SyncFSOp:
err = s.fs.SyncFS(ctx, typed)
case *fuseops.PollOp:
err = s.fs.Poll(ctx, typed)
} }
c.Reply(ctx, err) c.Reply(ctx, err)

View File

@ -60,12 +60,6 @@ func (fs *NotImplementedFileSystem) ForgetInode(
return fuse.ENOSYS return fuse.ENOSYS
} }
func (fs *NotImplementedFileSystem) BatchForget(
ctx context.Context,
op *fuseops.BatchForgetOp) error {
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) MkDir( func (fs *NotImplementedFileSystem) MkDir(
ctx context.Context, ctx context.Context,
op *fuseops.MkDirOp) error { op *fuseops.MkDirOp) error {
@ -144,6 +138,12 @@ func (fs *NotImplementedFileSystem) ReadFile(
return fuse.ENOSYS return fuse.ENOSYS
} }
func (fs *NotImplementedFileSystem) VectoredRead(
ctx context.Context,
op *fuseops.VectoredReadOp) error {
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) WriteFile( func (fs *NotImplementedFileSystem) WriteFile(
ctx context.Context, ctx context.Context,
op *fuseops.WriteFileOp) error { op *fuseops.WriteFileOp) error {
@ -204,20 +204,5 @@ func (fs *NotImplementedFileSystem) Fallocate(
return fuse.ENOSYS return fuse.ENOSYS
} }
func (fs *NotImplementedFileSystem) SyncFS(
ctx context.Context,
op *fuseops.SyncFSOp) error {
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) Poll(
ctx context.Context,
op *fuseops.PollOp) error {
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) SetConnection(*fuse.Connection) {
}
func (fs *NotImplementedFileSystem) Destroy() { func (fs *NotImplementedFileSystem) Destroy() {
} }

13
go.mod
View File

@ -1,19 +1,16 @@
module github.com/jacobsa/fuse module github.com/jacobsa/fuse
go 1.20 go 1.16
require ( require (
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff // indirect
github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11
github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb // indirect
github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3 github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3
github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6
github.com/kylelemons/godebug v1.1.0 github.com/kylelemons/godebug v1.1.0
golang.org/x/net v0.23.0 golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/sys v0.18.0 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
)
require (
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff // indirect
github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb // indirect
) )

11
go.sum
View File

@ -14,7 +14,10 @@ github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 h1:XKHJmHcgU9glxk
github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6/go.mod h1:JEWKD6V8xETMW+DEv+IQVz++f8Cn8O/X0HPeDY3qNis= github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6/go.mod h1:JEWKD6V8xETMW+DEv+IQVz++f8Cn8O/X0HPeDY3qNis=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -17,7 +17,6 @@ package buffer
import ( import (
"fmt" "fmt"
"io" "io"
"sync"
"syscall" "syscall"
"unsafe" "unsafe"
@ -53,38 +52,11 @@ func NewInMessage() *InMessage {
} }
} }
var readLock sync.Mutex
func (m *InMessage) ReadSingle(r io.Reader) (int, error) {
readLock.Lock()
defer readLock.Unlock()
// read request length
if _, err := io.ReadFull(r, m.storage[0:4]); err != nil {
return 0, err
}
l := m.Header().Len
// read remaining request
if n, err := io.ReadFull(r, m.storage[4:l]); err != nil {
return n, err
}
return int(l), nil
}
// Initialize with the data read by a single call to r.Read. The first call to // Initialize with the data read by a single call to r.Read. The first call to
// Consume will consume the bytes directly after the fusekernel.InHeader // Consume will consume the bytes directly after the fusekernel.InHeader
// struct. // struct.
func (m *InMessage) Init(r io.Reader) error { func (m *InMessage) Init(r io.Reader) error {
n, err := r.Read(m.storage[:])
var n int
var err error
if fusekernel.IsPlatformFuseT {
n, err = m.ReadSingle(r)
} else {
n, err = r.Read(m.storage[:])
}
if err != nil { if err != nil {
return err return err
} }

View File

@ -17,4 +17,4 @@ package buffer
// The maximum fuse write request size that InMessage can acommodate. // The maximum fuse write request size that InMessage can acommodate.
// //
// As of kernel 4.20 Linux accepts writes up to 256 pages or 1MiB // As of kernel 4.20 Linux accepts writes up to 256 pages or 1MiB
const MaxWriteSize = 1 << 17 const MaxWriteSize = 1 << 20

View File

@ -49,6 +49,47 @@ func findNonZero(p unsafe.Pointer, n int) int {
return n return n
} }
func TestMemclr(t *testing.T) {
// All sizes up to 32 bytes.
var sizes []int
for i := 0; i <= 32; i++ {
sizes = append(sizes, i)
}
// And a few hand-chosen sizes.
sizes = append(sizes, []int{
39, 41, 64, 127, 128, 129,
1<<20 - 1,
1 << 20,
1<<20 + 1,
}...)
// For each size, fill a buffer with random bytes and then zero it.
for _, size := range sizes {
size := size
t.Run(fmt.Sprintf("size=%d", size), func(t *testing.T) {
// Generate
b, err := randBytes(size)
if err != nil {
t.Fatalf("randBytes: %v", err)
}
// Clear
var p unsafe.Pointer
if len(b) != 0 {
p = unsafe.Pointer(&b[0])
}
jacobsa_fuse_memclr(p, uintptr(len(b)))
// Check
if i := findNonZero(p, len(b)); i != len(b) {
t.Fatalf("non-zero byte at offset %d", i)
}
})
}
}
func TestOutMessageAppend(t *testing.T) { func TestOutMessageAppend(t *testing.T) {
var om OutMessage var om OutMessage
om.Reset() om.Reset()
@ -193,7 +234,7 @@ func TestOutMessageReset(t *testing.T) {
} }
// Ensure a non-zero payload length. // Ensure a non-zero payload length.
om.Grow(128) p := om.Grow(128)
// Reset. // Reset.
om.Reset() om.Reset()

View File

@ -0,0 +1,31 @@
// 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 buffer
import "unsafe"
//go:noescape
// Zero the n bytes starting at p.
//
// REQUIRES: the region does not contain any Go pointers.
//go:linkname jacobsa_fuse_memclr runtime.memclrNoHeapPointers
func jacobsa_fuse_memclr(p unsafe.Pointer, n uintptr)
//go:noescape
// Copy from src to dst, allowing overlap.
//go:linkname jacobsa_fuse_memmove runtime.memmove
func jacobsa_fuse_memmove(dst unsafe.Pointer, src unsafe.Pointer, n uintptr)

View File

@ -0,0 +1,18 @@
// 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.
// The buffer package uses //go:linkname to push a few functions into this
// package but we still need a .s file so the Go tool does not pass -complete
// to the go tool compile so the latter does not complain about Go functions
// with no bodies.

View File

@ -41,14 +41,12 @@ import (
"unsafe" "unsafe"
) )
var IsPlatformFuseT bool
// The FUSE version implemented by the package. // The FUSE version implemented by the package.
const ( const (
ProtoVersionMinMajor = 7 ProtoVersionMinMajor = 7
ProtoVersionMinMinor = 18 ProtoVersionMinMinor = 19
ProtoVersionMaxMajor = 7 ProtoVersionMaxMajor = 7
ProtoVersionMaxMinor = 34 ProtoVersionMaxMinor = 31
) )
const ( const (
@ -170,7 +168,7 @@ const (
// OpenAccessModeMask is a bitmask that separates the access mode // OpenAccessModeMask is a bitmask that separates the access mode
// from the other flags in OpenFlags. // from the other flags in OpenFlags.
const OpenAccessModeMask OpenFlags = OpenReadOnly | OpenWriteOnly | OpenReadWrite const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE
// OpenFlags are the O_FOO flags passed to open/create/etc calls. For // OpenFlags are the O_FOO flags passed to open/create/etc calls. For
// example, os.O_WRONLY | os.O_APPEND. // example, os.O_WRONLY | os.O_APPEND.
@ -229,7 +227,6 @@ const (
OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file
OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open
OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X) OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X)
OpenCacheDir OpenResponseFlags = 1 << 3 // allow caching this directory
OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X
OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X
@ -243,7 +240,6 @@ var openResponseFlagNames = []flagName{
{uint32(OpenDirectIO), "OpenDirectIO"}, {uint32(OpenDirectIO), "OpenDirectIO"},
{uint32(OpenKeepCache), "OpenKeepCache"}, {uint32(OpenKeepCache), "OpenKeepCache"},
{uint32(OpenNonSeekable), "OpenNonSeekable"}, {uint32(OpenNonSeekable), "OpenNonSeekable"},
{uint32(OpenCacheDir), "OpenCacheDir"},
{uint32(OpenPurgeAttr), "OpenPurgeAttr"}, {uint32(OpenPurgeAttr), "OpenPurgeAttr"},
{uint32(OpenPurgeUBC), "OpenPurgeUBC"}, {uint32(OpenPurgeUBC), "OpenPurgeUBC"},
} }
@ -270,7 +266,6 @@ const (
InitAsyncDIO InitFlags = 1 << 15 InitAsyncDIO InitFlags = 1 << 15
InitWritebackCache InitFlags = 1 << 16 InitWritebackCache InitFlags = 1 << 16
InitNoOpenSupport InitFlags = 1 << 17 InitNoOpenSupport InitFlags = 1 << 17
InitParallelDirOps InitFlags = 1 << 18
InitMaxPages InitFlags = 1 << 22 InitMaxPages InitFlags = 1 << 22
InitCacheSymlinks InitFlags = 1 << 23 InitCacheSymlinks InitFlags = 1 << 23
InitNoOpendirSupport InitFlags = 1 << 24 InitNoOpendirSupport InitFlags = 1 << 24
@ -351,34 +346,6 @@ var releaseFlagNames = []flagName{
{uint32(ReleaseFlush), "ReleaseFlush"}, {uint32(ReleaseFlush), "ReleaseFlush"},
} }
// Poll flags and events are used in the Poll exchange.
type PollFlags uint32
const (
// From the kernel source:
// Ask for notification if there's someone waiting for it.
// The client may ignore the flag and always notify.
PollScheduleNotify PollFlags = 1 << 0
)
type PollEvents uint32
const (
PollInEvent PollEvents = 0x0001
PollPriEvent PollEvents = 0x0002
PollOutEvent PollEvents = 0x0004
PollErrEvent PollEvents = 0x0008
PollHupEvent PollEvents = 0x0010
PollNvalEvent PollEvents = 0x0020
PollRdNormEvent PollEvents = 0x0040
PollRdBandEvent PollEvents = 0x0080
PollWrNormEvent PollEvents = 0x0100
PollWrBandEvent PollEvents = 0x0200
PollMsgEvent PollEvents = 0x0400
PollRemoveEvent PollEvents = 0x1000
PollRdHupEvent PollEvents = 0x2000
)
// Opcodes // Opcodes
const ( const (
OpLookup = 1 OpLookup = 1
@ -419,17 +386,7 @@ const (
OpDestroy = 38 OpDestroy = 38
OpIoctl = 39 // Linux? OpIoctl = 39 // Linux?
OpPoll = 40 // Linux? OpPoll = 40 // Linux?
OpNotifyReply = 41
OpBatchForget = 42
OpFallocate = 43 OpFallocate = 43
OpReaddirplus = 44
//
OpRename2 = 45
OpLseek = 46
OpCopyFileRange = 47
OpSetupMapping = 48
OpRemoveMapping = 49
OpSyncFS = 50
// OS X // OS X
OpSetvolname = 61 OpSetvolname = 61
@ -460,16 +417,6 @@ type ForgetIn struct {
Nlookup uint64 Nlookup uint64
} }
type BatchForgetCountIn struct {
Count uint32
dummy uint32
}
type BatchForgetEntryIn struct {
Inode int64
Nlookup uint64
}
type GetattrIn struct { type GetattrIn struct {
GetattrFlags uint32 GetattrFlags uint32
dummy uint32 dummy uint32
@ -594,18 +541,6 @@ func CreateInSize(p Protocol) uintptr {
} }
} }
type PollIn struct {
Fh uint64
Kh uint64
Flags uint32
Events uint32
}
type PollOut struct {
Revents uint32
padding uint32
}
type ReleaseIn struct { type ReleaseIn struct {
Fh uint64 Fh uint64
Flags uint32 Flags uint32
@ -841,15 +776,8 @@ const (
NotifyCodePoll int32 = 1 NotifyCodePoll int32 = 1
NotifyCodeInvalInode int32 = 2 NotifyCodeInvalInode int32 = 2
NotifyCodeInvalEntry int32 = 3 NotifyCodeInvalEntry int32 = 3
NotifyCodeStore int32 = 4
NotifyCodeRetrieve int32 = 5
NotifyCodeDelete int32 = 6
) )
type NotifyPollWakeupOut struct {
Kh uint64
}
type NotifyInvalInodeOut struct { type NotifyInvalInodeOut struct {
Ino uint64 Ino uint64
Off int64 Off int64
@ -861,39 +789,3 @@ type NotifyInvalEntryOut struct {
Namelen uint32 Namelen uint32
padding uint32 padding uint32
} }
type SyncFSIn struct {
Padding uint64
}
type NotifyDeleteOut struct {
Parent uint64
Child uint64
Namelen uint32
padding uint32
}
type NotifyStoreOut struct {
Nodeid uint64
Offset uint64
Size uint32
padding uint32
}
type NotifyRetrieveOut struct {
Unique uint64
Nodeid uint64
Offset uint64
Size uint32
padding uint32
}
// Matches the size of WriteIn
type NotifyRetrieveIn struct {
dummy1 uint64
Offset uint64
Size uint32
dummy2 uint32
dummy3 uint64
dummy4 uint64
}

View File

@ -1,5 +1,3 @@
// +build linux windows
package fusekernel package fusekernel
import "time" import "time"

View File

@ -0,0 +1 @@
package fusekernel

View File

@ -17,11 +17,9 @@ package fuse
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"strings"
"syscall" "syscall"
) )
@ -43,8 +41,16 @@ func Mount(
config *MountConfig) (*MountedFileSystem, error) { config *MountConfig) (*MountedFileSystem, error) {
// Sanity check: make sure the mount point exists and is a directory. This // Sanity check: make sure the mount point exists and is a directory. This
// saves us from some confusing errors later on OS X. // saves us from some confusing errors later on OS X.
if err := checkMountPoint(dir); err != nil { fi, err := os.Stat(dir)
switch {
case os.IsNotExist(err):
return nil, err return nil, err
case err != nil:
return nil, fmt.Errorf("Statting mount point: %v", err)
case !fi.IsDir():
return nil, fmt.Errorf("Mount point %s is not a directory", dir)
} }
// Initialize the struct. // Initialize the struct.
@ -54,17 +60,11 @@ func Mount(
} }
// Begin the mounting process, which will continue in the background. // Begin the mounting process, which will continue in the background.
if config.DebugLogger != nil {
config.DebugLogger.Println("Beginning the mounting kickoff process")
}
ready := make(chan error, 1) ready := make(chan error, 1)
dev, err := mount(dir, config, ready) dev, err := mount(dir, config, ready)
if err != nil { if err != nil {
return nil, fmt.Errorf("mount: %v", err) return nil, fmt.Errorf("mount: %v", err)
} }
if config.DebugLogger != nil {
config.DebugLogger.Println("Completed the mounting kickoff process")
}
// Choose a parent context for ops. // Choose a parent context for ops.
cfgCopy := *config cfgCopy := *config
@ -72,9 +72,6 @@ func Mount(
cfgCopy.OpContext = context.Background() cfgCopy.OpContext = context.Background()
} }
if config.DebugLogger != nil {
config.DebugLogger.Println("Creating a connection object")
}
// Create a Connection object wrapping the device. // Create a Connection object wrapping the device.
connection, err := newConnection( connection, err := newConnection(
cfgCopy, cfgCopy,
@ -84,9 +81,6 @@ func Mount(
if err != nil { if err != nil {
return nil, fmt.Errorf("newConnection: %v", err) return nil, fmt.Errorf("newConnection: %v", err)
} }
if config.DebugLogger != nil {
config.DebugLogger.Println("Successfully created the connection")
}
// Serve the connection in the background. When done, set the join status. // Serve the connection in the background. When done, set the join status.
go func() { go func() {
@ -95,10 +89,6 @@ func Mount(
close(mfs.joinStatusAvailable) close(mfs.joinStatusAvailable)
}() }()
if config.DebugLogger != nil {
config.DebugLogger.Println("Waiting for mounting process to complete")
}
// Wait for the mount process to complete. // Wait for the mount process to complete.
if err := <-ready; err != nil { if err := <-ready; err != nil {
return nil, fmt.Errorf("mount (background): %v", err) return nil, fmt.Errorf("mount (background): %v", err)
@ -107,39 +97,13 @@ func Mount(
return mfs, nil return mfs, nil
} }
func checkMountPoint(dir string) error { func fusermount(binary string, argv []string, additionalEnv []string, wait bool) (*os.File, error) {
if strings.HasPrefix(dir, "/dev/fd") {
return nil
}
fi, err := os.Stat(dir)
switch {
case os.IsNotExist(err):
return err
case err != nil:
return fmt.Errorf("Statting mount point: %v", err)
case !fi.IsDir():
return fmt.Errorf("Mount point %s is not a directory", dir)
}
return nil
}
func fusermount(binary string, argv []string, additionalEnv []string, wait bool, debugLogger *log.Logger) (*os.File, error) {
if debugLogger != nil {
debugLogger.Println("Creating a socket pair")
}
// Create a socket pair. // Create a socket pair.
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("Socketpair: %v", err) return nil, fmt.Errorf("Socketpair: %v", err)
} }
if debugLogger != nil {
debugLogger.Println("Creating files to wrap the sockets")
}
// Wrap the sockets into os.File objects that we will pass off to fusermount. // Wrap the sockets into os.File objects that we will pass off to fusermount.
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
defer writeFile.Close() defer writeFile.Close()
@ -147,9 +111,6 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
defer readFile.Close() defer readFile.Close()
if debugLogger != nil {
debugLogger.Println("Starting fusermount/os mount")
}
// Start fusermount/mount_macfuse/mount_osxfuse. // Start fusermount/mount_macfuse/mount_osxfuse.
cmd := exec.Command(binary, argv...) cmd := exec.Command(binary, argv...)
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
@ -167,9 +128,6 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
return nil, fmt.Errorf("running %v: %v", binary, err) return nil, fmt.Errorf("running %v: %v", binary, err)
} }
if debugLogger != nil {
debugLogger.Println("Wrapping socket pair in a connection")
}
// Wrap the socket file in a connection. // Wrap the socket file in a connection.
c, err := net.FileConn(readFile) c, err := net.FileConn(readFile)
if err != nil { if err != nil {
@ -177,18 +135,12 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
} }
defer c.Close() defer c.Close()
if debugLogger != nil {
debugLogger.Println("Checking that we have a unix domain socket")
}
// We expect to have a Unix domain socket. // We expect to have a Unix domain socket.
uc, ok := c.(*net.UnixConn) uc, ok := c.(*net.UnixConn)
if !ok { if !ok {
return nil, fmt.Errorf("Expected UnixConn, got %T", c) return nil, fmt.Errorf("Expected UnixConn, got %T", c)
} }
if debugLogger != nil {
debugLogger.Println("Read a message from socket")
}
// Read a message. // Read a message.
buf := make([]byte, 32) // expect 1 byte buf := make([]byte, 32) // expect 1 byte
oob := make([]byte, 32) // expect 24 bytes oob := make([]byte, 32) // expect 24 bytes
@ -210,10 +162,6 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
scm := scms[0] scm := scms[0]
if debugLogger != nil {
debugLogger.Println("Successfully read the socket message.")
}
// Pull out the FD returned by fusermount // Pull out the FD returned by fusermount
gotFds, err := syscall.ParseUnixRights(&scm) gotFds, err := syscall.ParseUnixRights(&scm)
if err != nil { if err != nil {
@ -224,9 +172,6 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds) return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
} }
if debugLogger != nil {
debugLogger.Println("Converting FD into os.File")
}
// Turn the FD into an os.File. // Turn the FD into an os.File.
return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil
} }

View File

@ -50,7 +50,7 @@ type MountConfig struct {
// Linux only. OS X always behaves as if writeback caching is disabled. // Linux only. OS X always behaves as if writeback caching is disabled.
// //
// By default on Linux we allow the kernel to perform writeback caching // By default on Linux we allow the kernel to perform writeback caching
// (https://tinyurl.com/3ma8ypeu): // (cf. http://goo.gl/LdZzo1):
// //
// * When the user calls write(2), the kernel sticks the user's data into // * When the user calls write(2), the kernel sticks the user's data into
// its page cache. Only later does it call through to the file system, // its page cache. Only later does it call through to the file system,
@ -65,7 +65,7 @@ type MountConfig struct {
// //
// * close(2) (and anything else calling f_op->flush) causes all dirty // * close(2) (and anything else calling f_op->flush) causes all dirty
// pages to be written out before it proceeds to send a FlushFileOp // pages to be written out before it proceeds to send a FlushFileOp
// (https://tinyurl.com/3ur6vmsv). // (cf. https://goo.gl/TMrY6X).
// //
// * Similarly, close(2) causes the kernel to send a setattr request // * Similarly, close(2) causes the kernel to send a setattr request
// filling in the mtime if any dirty pages were flushed, since the time // filling in the mtime if any dirty pages were flushed, since the time
@ -78,23 +78,22 @@ type MountConfig struct {
// //
// Code walk: // Code walk:
// //
// * (https://tinyurl.com/3ur6vmsv) fuse_flush calls write_inode_now // * (https://goo.gl/zTIZQ9) fuse_flush calls write_inode_now before
// before calling the file system. The latter eventually calls into // calling the file system. The latter eventually calls into
// __writeback_single_inode. // __writeback_single_inode.
// //
// * (https://tinyurl.com/35vtmtsz) __writeback_single_inode calls // * (https://goo.gl/L7Z2w5) __writeback_single_inode calls
// do_writepages, which writes out any dirty pages. // do_writepages, which writes out any dirty pages.
// //
// * (https://tinyurl.com/3wv4paaf) __writeback_single_inode later // * (https://goo.gl/DOPgla) __writeback_single_inode later calls
// calls write_inode, which calls into the superblock op struct's // write_inode, which calls into the superblock op struct's write_inode
// write_inode member. For fuse, this is fuse_write_inode // member. For fuse, this is fuse_write_inode
// (https://tinyurl.com/mrxupe98). // (cf. https://goo.gl/eDSKOX).
// //
// * (https://tinyurl.com/mrxt9bta) fuse_write_inode calls // * (https://goo.gl/PbkGA1) fuse_write_inode calls fuse_flush_times.
// fuse_flush_times.
// //
// * (https://tinyurl.com/mr49cjdf) fuse_flush_times sends a setttr // * (https://goo.gl/ig8x9V) fuse_flush_times sends a setttr request
// request for setting the inode's mtime. // for setting the inode's mtime.
// //
// However, this brings along some caveats: // However, this brings along some caveats:
// //
@ -103,11 +102,11 @@ type MountConfig struct {
// //
// * The kernel caches mtime and ctime regardless of whether the file // * The kernel caches mtime and ctime regardless of whether the file
// system tells it to do so, disregarding the result of further getattr // system tells it to do so, disregarding the result of further getattr
// requests (https://tinyurl.com/mrxnfatv, https://tinyurl.com/27jju8n4). // requests (cf. https://goo.gl/3ZZMUw, https://goo.gl/7WtQUp). It
// It appears this may be true of the file size, too. Writeback caching // appears this may be true of the file size, too. Writeback caching may
// may therefore not be suitable for file systems where these attributes // therefore not be suitable for file systems where these attributes can
// can spontaneously change for reasons the kernel doesn't observe. See // spontaneously change for reasons the kernel doesn't observe. See
// https://tinyurl.com/yyprvjvs for more discussion. // http://goo.gl/V5WQCN for more discussion.
// //
// Setting DisableWritebackCaching disables this behavior. Instead the file // Setting DisableWritebackCaching disables this behavior. Instead the file
// system is called one or more times for each write(2), and the user's // system is called one or more times for each write(2), and the user's
@ -117,12 +116,12 @@ type MountConfig struct {
// OS X only. // OS X only.
// //
// Normally on OS X we mount with the novncache option // Normally on OS X we mount with the novncache option
// (https://tinyurl.com/52hz9vya), which disables entry caching in the // (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel.
// kernel. This is because macFUSE (osxfuse) does not honor the entry // This is because osxfuse does not honor the entry expiration values we
// expiration values we return to it, instead caching potentially forever // return to it, instead caching potentially forever (cf.
// (https://tinyurl.com/2rr6cd3m), and it is probably better to fail to cache // http://goo.gl/8yR0Ie), and it is probably better to fail to cache than to
// than to cache for too long, since the latter is more likely to hide // cache for too long, since the latter is more likely to hide consistency
// consistency bugs that are difficult to detect and diagnose. // bugs that are difficult to detect and diagnose.
// //
// This field disables the use of novncache, restoring entry caching. Beware: // This field disables the use of novncache, restoring entry caching. Beware:
// the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and // the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and
@ -152,36 +151,22 @@ type MountConfig struct {
// OpenDir calls at all (Linux >= 5.1): // OpenDir calls at all (Linux >= 5.1):
EnableNoOpendirSupport bool EnableNoOpendirSupport bool
// Tell the kernel to use READDIRPLUS.
// Note that the implementation may still fall back to READDIR if the running
// kernel doesn't have support for READDIRPLUS.
UseReadDirPlus bool
// Disable FUSE default permissions. // Disable FUSE default permissions.
// This is useful for situations where the backing data store (e.g., S3) doesn't // This is useful for situations where the backing data store (e.g., S3) doesn't
// actually utilise any form of qualifiable UNIX permissions. // actually utilise any form of qualifiable UNIX permissions.
DisableDefaultPermissions bool DisableDefaultPermissions bool
// Use vectored reads. // Use VectoredReadOp instead of ReadFileOp.
// Vectored read allows file systems to avoid memory copying overhead if // Vectored read allows file systems to reduce memory copying overhead if
// the data is already in memory when they return it to FUSE. // the data is already in memory when they return it to FUSE.
// When turned on, ReadFileOp.Dst is always nil and the FS must return data
// being read from the file as a list of slices in ReadFileOp.Data.
UseVectoredRead bool UseVectoredRead bool
// OS X only. // OS X only.
// //
// The name of the mounted volume, as displayed in the Finder. If empty, a // The name of the mounted volume, as displayed in the Finder. If empty, a
// default name involving the string 'osxfuse' (the old name of macFUSE) // default name involving the string 'osxfuse' is used.
// is used.
VolumeName string VolumeName string
// OS X only.
//
// The FUSE implementation to use. One of FUSEImplFuseT (default) or
// FUSEImplMacFUSE.
FuseImpl FUSEImpl
// Additional key=value options to pass unadulterated to the underlying mount // Additional key=value options to pass unadulterated to the underlying mount
// command. See `man 8 mount`, the fuse documentation, etc. for // command. See `man 8 mount`, the fuse documentation, etc. for
// system-specific information. // system-specific information.
@ -198,20 +183,8 @@ type MountConfig struct {
// Flag to enable async reads that are received from // Flag to enable async reads that are received from
// the kernel // the kernel
EnableAsyncReads bool EnableAsyncReads bool
// Flag to enable parallel lookup and readdir operations from the
// kernel
// Ref: https://github.com/torvalds/linux/commit/5c672ab3f0ee0f78f7acad183f34db0f8781a200
EnableParallelDirOps bool
} }
type FUSEImpl uint8
const (
FUSEImplFuseT = iota
FUSEImplMacFUSE
)
// Create a map containing all of the key=value mount options to be given to // Create a map containing all of the key=value mount options to be given to
// the mount helper. // the mount helper.
func (c *MountConfig) toMap() (opts map[string]string) { func (c *MountConfig) toMap() (opts map[string]string) {

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"strconv" "strconv"
@ -12,7 +11,6 @@ import (
"syscall" "syscall"
"github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/fuse/internal/fusekernel"
) )
var errNoAvail = errors.New("no available fuse devices") var errNoAvail = errors.New("no available fuse devices")
@ -80,8 +78,6 @@ var (
} }
) )
const FUSET_SRV_PATH = "/usr/local/bin/go-nfsv4"
func loadOSXFUSE(bin string) error { func loadOSXFUSE(bin string) error {
cmd := exec.Command(bin) cmd := exec.Command(bin)
cmd.Dir = "/" cmd.Dir = "/"
@ -209,14 +205,14 @@ func callMountCommFD(
env = append(env, "_FUSE_COMMVERS=2") env = append(env, "_FUSE_COMMVERS=2")
argv = append(argv, dir) argv = append(argv, dir)
return fusermount(bin, argv, env, false, cfg.DebugLogger) return fusermount(bin, argv, env, false)
} }
// Begin the process of mounting at the given directory, returning a connection // Begin the process of mounting at the given directory, returning a connection
// to the kernel. Mounting continues in the background, and is complete when an // to the kernel. Mounting continues in the background, and is complete when an
// error is written to the supplied channel. The file system may need to // error is written to the supplied channel. The file system may need to
// service the connection in order for mounting to complete. // service the connection in order for mounting to complete.
func mountOsxFuse( func mount(
dir string, dir string,
cfg *MountConfig, cfg *MountConfig,
ready chan<- error) (dev *os.File, err error) { ready chan<- error) (dev *os.File, err error) {
@ -267,161 +263,3 @@ func mountOsxFuse(
return nil, errOSXFUSENotFound return nil, errOSXFUSENotFound
} }
func fusetBinary() (string, error) {
srv_path := os.Getenv("FUSE_NFSSRV_PATH")
if srv_path == "" {
srv_path = FUSET_SRV_PATH
}
if _, err := os.Stat(srv_path); err == nil {
return srv_path, nil
}
return "", fmt.Errorf("FUSE-T not found")
}
func unixgramSocketpair() (l, r *os.File, err error) {
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, nil, os.NewSyscallError("socketpair",
err.(syscall.Errno))
}
l = os.NewFile(uintptr(fd[0]), fmt.Sprintf("socketpair-half%d", fd[0]))
r = os.NewFile(uintptr(fd[1]), fmt.Sprintf("socketpair-half%d", fd[1]))
return
}
var local, local_mon, remote, remote_mon *os.File
func startFuseTServer(binary string, argv []string,
additionalEnv []string,
wait bool,
debugLogger *log.Logger,
ready chan<- error) (*os.File, error) {
if debugLogger != nil {
debugLogger.Println("Creating a socket pair")
}
var err error
local, remote, err = unixgramSocketpair()
if err != nil {
return nil, err
}
defer remote.Close()
local_mon, remote_mon, err = unixgramSocketpair()
if err != nil {
return nil, err
}
defer remote_mon.Close()
syscall.CloseOnExec(int(local.Fd()))
syscall.CloseOnExec(int(local_mon.Fd()))
if debugLogger != nil {
debugLogger.Println("Creating files to wrap the sockets")
}
if debugLogger != nil {
debugLogger.Println("Starting fusermount/os mount")
}
// Start fusermount/mount_macfuse/mount_osxfuse.
cmd := exec.Command(binary, argv...)
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
cmd.Env = append(cmd.Env, "_FUSE_MONFD=4")
cmd.Env = append(cmd.Env, additionalEnv...)
cmd.ExtraFiles = []*os.File{remote, remote_mon}
cmd.Stderr = nil
cmd.Stdout = nil
// daemonize
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true,
}
// Run the command.
err = cmd.Start()
cmd.Process.Release()
if err != nil {
return nil, fmt.Errorf("running %v: %v", binary, err)
}
if debugLogger != nil {
debugLogger.Println("Wrapping socket pair in a connection")
}
if debugLogger != nil {
debugLogger.Println("Checking that we have a unix domain socket")
}
if debugLogger != nil {
debugLogger.Println("Read a message from socket")
}
go func() {
if _, err = local_mon.Write([]byte("mount")); err != nil {
err = fmt.Errorf("fuse-t failed: %v", err)
} else {
reply := make([]byte, 4)
if _, err = local_mon.Read(reply); err != nil {
fmt.Printf("mount read %v\n", err)
err = fmt.Errorf("fuse-t failed: %v", err)
}
}
ready <- err
close(ready)
}()
if debugLogger != nil {
debugLogger.Println("Successfully read the socket message.")
}
return local, nil
}
func mountFuset(
dir string,
cfg *MountConfig,
ready chan<- error) (dev *os.File, err error) {
fuseTBin, err := fusetBinary()
if err != nil {
return nil, err
}
fusekernel.IsPlatformFuseT = true
env := []string{}
argv := []string{
fmt.Sprintf("--rwsize=%d", buffer.MaxWriteSize),
}
if cfg.VolumeName != "" {
argv = append(argv, "--volname")
argv = append(argv, cfg.VolumeName)
}
if cfg.ReadOnly {
argv = append(argv, "-r")
}
env = append(env, "_FUSE_COMMVERS=2")
argv = append(argv, dir)
return startFuseTServer(fuseTBin, argv, env, false, cfg.DebugLogger, ready)
}
func mount(
dir string,
cfg *MountConfig,
ready chan<- error) (dev *os.File, err error) {
fusekernel.IsPlatformFuseT = false
switch cfg.FuseImpl {
case FUSEImplMacFUSE:
dev, err = mountOsxFuse(dir, cfg, ready)
case FUSEImplFuseT:
fallthrough
default:
dev, err = mountFuset(dir, cfg, ready)
}
return
}

View File

@ -5,8 +5,6 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strconv"
"strings"
"syscall" "syscall"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -55,9 +53,6 @@ var mountflagopts = map[string]func(uintptr) uintptr{
var errFallback = errors.New("sentinel: fallback to fusermount(1)") var errFallback = errors.New("sentinel: fallback to fusermount(1)")
func directmount(dir string, cfg *MountConfig) (*os.File, error) { func directmount(dir string, cfg *MountConfig) (*os.File, error) {
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Preparing for direct mounting")
}
// We use syscall.Open + os.NewFile instead of os.OpenFile so that the file // We use syscall.Open + os.NewFile instead of os.OpenFile so that the file
// is opened in blocking mode. When opened in non-blocking mode, the Go // is opened in blocking mode. When opened in non-blocking mode, the Go
// runtime tries to use poll(2), which does not work with /dev/fuse. // runtime tries to use poll(2), which does not work with /dev/fuse.
@ -66,10 +61,6 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
return nil, errFallback return nil, errFallback
} }
dev := os.NewFile(uintptr(fd), "/dev/fuse") dev := os.NewFile(uintptr(fd), "/dev/fuse")
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Successfully opened the /dev/fuse in blocking mode")
}
// As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847 // As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847
data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d", data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d",
dev.Fd(), os.Getuid(), os.Getgid()) dev.Fd(), os.Getuid(), os.Getgid())
@ -84,7 +75,6 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
mountflag = fn(mountflag) mountflag = fn(mountflag)
delete(opts, k) delete(opts, k)
} }
fsname := opts["fsname"]
delete(opts, "fsname") // handled via fstype mount(2) parameter delete(opts, "fsname") // handled via fstype mount(2) parameter
fstype := "fuse" fstype := "fuse"
if subtype, ok := opts["subtype"]; ok { if subtype, ok := opts["subtype"]; ok {
@ -92,16 +82,12 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
} }
delete(opts, "subtype") delete(opts, "subtype")
data += "," + mapToOptionsString(opts) data += "," + mapToOptionsString(opts)
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Starting the unix mounting")
}
if err := unix.Mount( if err := unix.Mount(
fsname, // source cfg.FSName, // source
dir, // target dir, // target
fstype, // fstype fstype, // fstype
mountflag, // mountflag mountflag, // mountflag
data, // data data, // data
); err != nil { ); err != nil {
if err == syscall.EPERM { if err == syscall.EPERM {
return nil, errFallback return nil, errFallback
@ -109,9 +95,6 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
} }
return nil, err return nil, err
} }
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Unix mounting completed successfully")
}
return dev, nil return dev, nil
} }
@ -123,24 +106,10 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
// On linux, mounting is never delayed. // On linux, mounting is never delayed.
ready <- nil ready <- nil
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Parsing fuse file descriptor")
}
// If the mountpoint is /dev/fd/N, assume that the file descriptor N is an
// already open FUSE channel. Parse it, cast it to an fd, and don't do any
// other part of the mount dance.
if fd, err := parseFuseFd(dir); err == nil {
dev := os.NewFile(uintptr(fd), "/dev/fuse")
return dev, nil
}
// Try mounting without fusermount(1) first: we might be running as root or // Try mounting without fusermount(1) first: we might be running as root or
// have the CAP_SYS_ADMIN capability. // have the CAP_SYS_ADMIN capability.
dev, err := directmount(dir, cfg) dev, err := directmount(dir, cfg)
if err == errFallback { if err == errFallback {
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Directmount failed. Trying fallback.")
}
fusermountPath, err := findFusermount() fusermountPath, err := findFusermount()
if err != nil { if err != nil {
return nil, err return nil, err
@ -150,20 +119,7 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
"--", "--",
dir, dir,
} }
return fusermount(fusermountPath, argv, []string{}, true, cfg.DebugLogger) return fusermount(fusermountPath, argv, []string{}, true)
} }
return dev, err return dev, err
} }
func parseFuseFd(dir string) (int, error) {
if !strings.HasPrefix(dir, "/dev/fd/") {
return -1, fmt.Errorf("not a /dev/fd path")
}
fd, err := strconv.ParseUint(strings.TrimPrefix(dir, "/dev/fd/"), 10, 32)
if err != nil {
return -1, fmt.Errorf("invalid /dev/fd/N path: N must be a positive integer")
}
return int(fd), nil
}

View File

@ -1,37 +0,0 @@
package fuse
import (
"testing"
)
func Test_parseFuseFd(t *testing.T) {
t.Run("valid", func(t *testing.T) {
fd, err := parseFuseFd("/dev/fd/42")
if fd != 42 {
t.Errorf("expected 42, got %d", fd)
}
if err != nil {
t.Errorf("expected no error, got %#v", err)
}
})
t.Run("negative", func(t *testing.T) {
fd, err := parseFuseFd("/dev/fd/-42")
if fd != -1 {
t.Errorf("expected an invalid fd, got %d", fd)
}
if err == nil {
t.Errorf("expected an error, nil")
}
})
t.Run("not an int", func(t *testing.T) {
fd, err := parseFuseFd("/dev/fd/3.14159")
if fd != -1 {
t.Errorf("expected an invalid fd, got %d", fd)
}
if err == nil {
t.Errorf("expected an error, nil")
}
})
}

View File

@ -14,10 +14,7 @@
package fuse package fuse
import ( import "context"
"context"
"fmt"
)
// MountedFileSystem represents the status of a mount operation, with a method // MountedFileSystem represents the status of a mount operation, with a method
// that waits for unmounting. // that waits for unmounting.
@ -50,17 +47,3 @@ func (mfs *MountedFileSystem) Join(ctx context.Context) error {
return ctx.Err() return ctx.Err()
} }
} }
// GetFuseContext implements the equiv. of FUSE-C fuse_get_context() and thus
// returns the UID / GID / PID associated with all FUSE requests send by the kernel.
// ctx parameter must be one of the context from the fuseops handlers (e.g.: CreateFile)
func (mfs *MountedFileSystem) GetFuseContext(ctx context.Context) (uid, gid, pid uint32, err error) {
foo := ctx.Value(contextKey)
state, ok := foo.(opState)
if !ok {
return 0, 0, 0, fmt.Errorf("GetFuseContext called with invalid context: %#v", ctx)
}
inMsg := state.inMsg
header := inMsg.Header()
return header.Uid, header.Gid, header.Pid, nil
}

View File

@ -36,9 +36,9 @@ const (
// A file system with a fixed structure that looks like this: // A file system with a fixed structure that looks like this:
// //
// foo // foo
// dir/ // dir/
// bar // bar
// //
// The file system is configured with durations that specify how long to allow // The file system is configured with durations that specify how long to allow
// inode entries and attributes to be cached, used when responding to fuse // inode entries and attributes to be cached, used when responding to fuse
@ -48,11 +48,6 @@ const (
// Each file responds to reads with random contents. SetKeepCache can be used // Each file responds to reads with random contents. SetKeepCache can be used
// to control whether the response to OpenFileOp tells the kernel to keep the // to control whether the response to OpenFileOp tells the kernel to keep the
// file's data in the page cache or not. // file's data in the page cache or not.
//
// Each directory responds to readdir with random entries (different names).
// SetCacheDir and SetKeepDirCache can be used to control whether the response
// to OpenDirOp tells the kernel to cache the next response of ReadDirOp
// in cache, or invalidate the existing cached entry in page cache.
type CachingFS interface { type CachingFS interface {
fuseutil.FileSystem fuseutil.FileSystem
@ -71,27 +66,20 @@ type CachingFS interface {
// Instruct the file system whether or not to reply to OpenFileOp with // Instruct the file system whether or not to reply to OpenFileOp with
// FOPEN_KEEP_CACHE set. // FOPEN_KEEP_CACHE set.
SetKeepCache(keep bool) SetKeepCache(keep bool)
// Instruct the file system whether or not to reply to OpenDirOp with
// FOPEN_KEEP_CACHE set.
SetKeepDirCache(keep bool)
// Instruct the file system whether or not to reply to OpenDirOp with
// FOPEN_CACHE_DIR set.
SetCacheDir(cacheDir bool)
} }
// Create a file system that issues cacheable responses according to the // Create a file system that issues cacheable responses according to the
// following rules: // following rules:
// //
// - LookUpInodeResponse.Entry.EntryExpiration is set according to // * LookUpInodeResponse.Entry.EntryExpiration is set according to
// lookupEntryTimeout. // lookupEntryTimeout.
// //
// - GetInodeAttributesResponse.AttributesExpiration is set according to // * GetInodeAttributesResponse.AttributesExpiration is set according to
// getattrTimeout. // getattrTimeout.
// //
// - Nothing else is marked cacheable. (In particular, the attributes // * Nothing else is marked cacheable. (In particular, the attributes
// returned by LookUpInode are not cacheable.) // returned by LookUpInode are not cacheable.)
//
func NewCachingFS( func NewCachingFS(
lookupEntryTimeout time.Duration, lookupEntryTimeout time.Duration,
getattrTimeout time.Duration) (CachingFS, error) { getattrTimeout time.Duration) (CachingFS, error) {
@ -139,10 +127,6 @@ type cachingFS struct {
// GUARDED_BY(mu) // GUARDED_BY(mu)
keepPageCache bool keepPageCache bool
// GUARDED_BY(mu)
keepDirCache bool
cacheDir bool
// The current ID of the lowest numbered non-root inode. // The current ID of the lowest numbered non-root inode.
// //
// INVARIANT: baseID > fuseops.RootInodeID // INVARIANT: baseID > fuseops.RootInodeID
@ -219,17 +203,6 @@ func (fs *cachingFS) barAttrs() fuseops.InodeAttributes {
} }
} }
func randomString(len int) string {
bytes := make([]byte, len)
nn, err := rand.Read(bytes)
if err != nil || len != nn {
return ""
}
return string(bytes)
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Public interface // Public interface
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -282,22 +255,6 @@ func (fs *cachingFS) SetKeepCache(keep bool) {
fs.keepPageCache = keep fs.keepPageCache = keep
} }
// LOCKS_EXCLUDED(fs.mu)
func (fs *cachingFS) SetKeepDirCache(keep bool) {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.keepDirCache = keep
}
// LOCKS_EXCLUDED(fs.mu)
func (fs *cachingFS) SetCacheDir(cacheDir bool) {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.cacheDir = cacheDir
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// FileSystem methods // FileSystem methods
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -393,51 +350,6 @@ func (fs *cachingFS) GetInodeAttributes(
func (fs *cachingFS) OpenDir( func (fs *cachingFS) OpenDir(
ctx context.Context, ctx context.Context,
op *fuseops.OpenDirOp) error { op *fuseops.OpenDirOp) error {
fs.mu.Lock()
defer fs.mu.Unlock()
op.CacheDir = fs.cacheDir
op.KeepCache = fs.keepDirCache
return nil
}
func (fs *cachingFS) ReadDir(
ctx context.Context,
op *fuseops.ReadDirOp) error {
entries := []fuseutil.Dirent{
{
Offset: 1,
Inode: 101,
Name: "rdir" + randomString(4),
Type: fuseutil.DT_Directory,
},
{
Offset: 2,
Inode: 102,
Name: "rfoo" + randomString(4),
Type: fuseutil.DT_File,
},
}
// Grab the range of interest.
if op.Offset > fuseops.DirOffset(len(entries)) {
return fuse.EIO
}
entries = entries[op.Offset:]
// Resume at the specified offset into the array.
for _, e := range entries {
n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], e)
if n == 0 {
break
}
op.BytesRead += n
}
return nil return nil
} }
@ -456,6 +368,8 @@ func (fs *cachingFS) ReadFile(
ctx context.Context, ctx context.Context,
op *fuseops.ReadFileOp) error { op *fuseops.ReadFileOp) error {
var err error var err error
op.BytesRead, err = io.ReadFull(rand.Reader, op.Dst) dst := make([]byte, op.Size)
op.BytesRead, err = io.ReadFull(rand.Reader, dst)
op.Data = [][]byte{dst}
return err return err
} }

View File

@ -723,253 +723,3 @@ func (t *PageCacheTest) TwoFileHandles_KeepCache() {
ExpectTrue(bytes.Equal(c1, c3)) ExpectTrue(bytes.Equal(c1, c3))
} }
////////////////////////////////////////////////////////////////////////
// Dir cache
////////////////////////////////////////////////////////////////////////
type DirCacheTest struct {
cachingFSTest
}
var _ SetUpInterface = &DirCacheTest{}
func init() { RegisterTestSuite(&DirCacheTest{}) }
func (t *DirCacheTest) SetUp(ti *TestInfo) {
const (
lookupEntryTimeout = 0
getattrTimeout = 0
)
t.cachingFSTest.setUp(ti, lookupEntryTimeout, getattrTimeout)
}
func (t *DirCacheTest) CacheDirAndKeepDirCache() {
t.fs.SetCacheDir(true)
t.fs.SetKeepDirCache(true)
// First read, kernel will cache the dir response.
f, err := os.Open(path.Join(t.Dir, "dir"))
defer f.Close() // Make sure to close specially required in failure scenario.
AssertEq(nil, err)
names1, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names1))
err = f.Close()
AssertEq(nil, err)
// Second read, will be served from cache.
f, err = os.Open(path.Join(t.Dir, "dir"))
AssertEq(nil, err)
names2, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names2))
err = f.Close()
AssertEq(nil, err)
AssertEq(names1[0], names2[0])
AssertEq(names1[1], names2[1])
}
func (t *DirCacheTest) NoCacheDirAndKeepDirCache() {
t.fs.SetCacheDir(false)
t.fs.SetKeepDirCache(true)
// First read, no caching since NoCacheDir.
f, err := os.Open(path.Join(t.Dir, "dir"))
defer f.Close() // Make sure to close specially required in failure scenario.
AssertEq(nil, err)
names1, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names1))
err = f.Close()
AssertEq(nil, err)
// Second read, will be served from filesystem, hence different name.
f, err = os.Open(path.Join(t.Dir, "dir"))
AssertEq(nil, err)
names2, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names2))
err = f.Close()
AssertEq(nil, err)
AssertNe(names1[0], names2[0])
AssertNe(names1[1], names2[1])
}
func (t *DirCacheTest) CacheDirAndNoKeepDirCache() {
t.fs.SetCacheDir(true)
t.fs.SetKeepDirCache(false)
// First read, kernel will cache the dir response.
f, err := os.Open(path.Join(t.Dir, "dir"))
defer f.Close() // Make sure to close specially required in failure scenario.
AssertEq(nil, err)
names1, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names1))
err = f.Close()
AssertEq(nil, err)
// Second read, cached response will be invalidated since NoKeepDirCache.
// Hence, different names.
f, err = os.Open(path.Join(t.Dir, "dir"))
AssertEq(nil, err)
names2, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names2))
err = f.Close()
AssertEq(nil, err)
AssertNe(names1[0], names2[0])
AssertNe(names1[1], names2[1])
}
func (t *DirCacheTest) NoCacheDirAndNoKeepDirCache() {
t.fs.SetCacheDir(false)
t.fs.SetKeepDirCache(false)
// First read, no caching since NoCacheDir.
f, err := os.Open(path.Join(t.Dir, "dir"))
defer f.Close() // Make sure to close specially required in failure scenario.
AssertEq(nil, err)
names1, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names1))
err = f.Close()
AssertEq(nil, err)
// Second read, will be served from filesystem.
// Since NoCacheDir also NoKeepDirCache. Hence, different names.
f, err = os.Open(path.Join(t.Dir, "dir"))
AssertEq(nil, err)
names2, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names2))
err = f.Close()
AssertEq(nil, err)
AssertNe(names1[0], names2[0])
AssertNe(names1[1], names2[1])
}
func (t *DirCacheTest) CacheDirWithChangingKeepDirCache() {
t.fs.SetCacheDir(true)
t.fs.SetKeepDirCache(false)
// First read, kernel will cache the dir response.
f, err := os.Open(path.Join(t.Dir, "dir"))
defer f.Close() // Make sure to close specially required in failure scenario.
AssertEq(nil, err)
names1, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names1))
err = f.Close()
AssertEq(nil, err)
// Cached response will not be invalidated. Hence, served from kernel cache.
t.fs.SetKeepDirCache(true)
// Second read, will be served from cache
f, err = os.Open(path.Join(t.Dir, "dir"))
AssertEq(nil, err)
names2, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names2))
err = f.Close()
AssertEq(nil, err)
AssertEq(names1[0], names2[0])
AssertEq(names1[1], names2[1])
// Kernel has cached but invalidated due to NoKeepDirCache.
// Hence, third read will be served from filesystem.
t.fs.SetKeepDirCache(false)
// Third read, will be served from filesystem. So, names will be different.
f, err = os.Open(path.Join(t.Dir, "dir"))
AssertEq(nil, err)
names3, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names3))
err = f.Close()
AssertEq(nil, err)
AssertNe(names2[0], names3[0])
AssertNe(names2[1], names3[1])
}
func (t *DirCacheTest) ChangingCacheDirWithKeepDirCache() {
t.fs.SetCacheDir(true)
t.fs.SetKeepDirCache(true)
// First read, kernel will cache the dir response.
f, err := os.Open(path.Join(t.Dir, "dir"))
defer f.Close() // Make sure to close specially required in failure scenario.
AssertEq(nil, err)
names1, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names1))
err = f.Close()
AssertEq(nil, err)
// Cached response will not be invalidated, but also not be served from cache.
// Since NoCacheDir so names will be different.
t.fs.SetCacheDir(false)
// Second read, will be served from filesystem.
f, err = os.Open(path.Join(t.Dir, "dir"))
AssertEq(nil, err)
names2, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names2))
err = f.Close()
AssertEq(nil, err)
AssertNe(names1[0], names2[0])
AssertNe(names1[1], names2[1])
// Third read will be served from cache.
// But first read response is cached, since KeepDirCache.
t.fs.SetCacheDir(true)
// Third read, will be served from filesystem. So, names will be different.
f, err = os.Open(path.Join(t.Dir, "dir"))
AssertEq(nil, err)
names3, err := f.Readdirnames(-1)
AssertEq(nil, err)
AssertEq(2, len(names3))
err = f.Close()
AssertEq(nil, err)
AssertEq(names1[0], names3[0])
AssertEq(names1[1], names3[1])
}

View File

@ -246,7 +246,8 @@ func (fs *dynamicFS) ReadFile(
} }
reader := strings.NewReader(contents) reader := strings.NewReader(contents)
var err error var err error
op.BytesRead, err = reader.ReadAt(op.Dst, op.Offset) op.Data = [][]byte{ make([]byte, op.Size) }
op.BytesRead, err = reader.ReadAt(op.Data[0], op.Offset)
if err == io.EOF { if err == io.EOF {
return nil return nil
} }

View File

@ -26,7 +26,7 @@ import (
"github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/fuse/fuseutil"
) )
const FooContents = "xxxx" var FooContents = []byte("xxxx")
const fooInodeID = fuseops.RootInodeID + 1 const fooInodeID = fuseops.RootInodeID + 1
@ -171,7 +171,8 @@ func (fs *errorFS) ReadFile(
return fmt.Errorf("Unexpected request: %#v", op) return fmt.Errorf("Unexpected request: %#v", op)
} }
op.BytesRead = copy(op.Dst, FooContents) op.Data = [][]byte{FooContents}
op.BytesRead = len(FooContents)
return nil return nil
} }

View File

@ -196,7 +196,12 @@ func (fs *flushFS) ReadFile(
} }
// Read what we can. // Read what we can.
op.BytesRead = copy(op.Dst, fs.fooContents[op.Offset:]) end := op.Offset+op.Size
if end > int64(len(fs.fooContents)) {
end = int64(len(fs.fooContents))
}
op.Data = [][]byte{ fs.fooContents[op.Offset : end] }
op.BytesRead = int(end-op.Offset)
return nil return nil
} }

View File

@ -28,9 +28,9 @@ import (
// Create a file system with a fixed structure that looks like this: // Create a file system with a fixed structure that looks like this:
// //
// hello // hello
// dir/ // dir/
// world // world
// //
// Each file contains the string "Hello, world!". // Each file contains the string "Hello, world!".
func NewHelloFS(clock timeutil.Clock) (fuse.Server, error) { func NewHelloFS(clock timeutil.Clock) (fuse.Server, error) {
@ -218,7 +218,7 @@ func (fs *helloFS) ReadDir(
// Grab the range of interest. // Grab the range of interest.
if op.Offset > fuseops.DirOffset(len(entries)) { if op.Offset > fuseops.DirOffset(len(entries)) {
return nil return fuse.EIO
} }
entries = entries[op.Offset:] entries = entries[op.Offset:]
@ -250,7 +250,8 @@ func (fs *helloFS) ReadFile(
reader := strings.NewReader("Hello, world!") reader := strings.NewReader("Hello, world!")
var err error var err error
op.BytesRead, err = reader.ReadAt(op.Dst, op.Offset) op.Data = [][]byte{ make([]byte, op.Size) }
op.BytesRead, err = reader.ReadAt(op.Data[0], op.Offset)
// Special case: FUSE doesn't expect us to return io.EOF. // Special case: FUSE doesn't expect us to return io.EOF.
if err == io.EOF { if err == io.EOF {

View File

@ -1,81 +0,0 @@
package memfs_test
import (
"os"
"path"
"testing"
"github.com/jacobsa/fuse/samples"
"github.com/jacobsa/fuse/samples/memfs"
. "github.com/jacobsa/ogletest"
)
func TestFuseServerCallbacks(t *testing.T) { RunTests(t) }
type CallbackTest struct {
samples.SampleTest
readFileCallbackInvoked bool
writeFileCallbackInvoked bool
}
func init() { RegisterTestSuite(&CallbackTest{}) }
func (t *CallbackTest) SetUp(ti *TestInfo) {
t.MountConfig.DisableWritebackCaching = true
t.readFileCallbackInvoked = false
t.writeFileCallbackInvoked = false
t.Server = memfs.NewMemFSWithCallbacks(
currentUid(),
currentGid(),
func() { t.readFileCallbackInvoked = true },
func() { t.writeFileCallbackInvoked = true },
)
t.SampleTest.SetUp(ti)
}
// The test suite is torn down during the test to ensure
// that all FUSE operations are complete before checking
// the invocations on the callbacks.
func (t *CallbackTest) TearDown() {}
func (t *CallbackTest) TestCallbacksInvokedForWriteFile() {
AssertEq(t.writeFileCallbackInvoked, false)
AssertEq(t.readFileCallbackInvoked, false)
// Write a file.
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
const contents = "Hello world"
err := os.WriteFile(fileName, []byte(contents), 0400)
AssertEq(nil, err)
// Tear down the FUSE mount. This ensures that all FUSE operations are complete.
t.SampleTest.TearDown()
// Check that our callback was invoked as expected.
AssertEq(t.writeFileCallbackInvoked, true)
AssertEq(t.readFileCallbackInvoked, false)
}
func (t *CallbackTest) TestCallbacksInvokedForWriteFileAndReadFile() {
AssertEq(t.writeFileCallbackInvoked, false)
AssertEq(t.readFileCallbackInvoked, false)
// Write a file.
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
const contents = "Hello world"
err := os.WriteFile(fileName, []byte(contents), 0400)
AssertEq(nil, err)
// Read it back.
slice, err := os.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq(contents, string(slice))
// Tear down the FUSE mount. This ensures that all FUSE operations are complete.
t.SampleTest.TearDown()
// Check that our callback was invoked as expected.
AssertEq(t.writeFileCallbackInvoked, true)
AssertEq(t.readFileCallbackInvoked, true)
}

View File

@ -17,7 +17,6 @@ package memfs
import ( import (
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"time" "time"
@ -34,10 +33,6 @@ type inode struct {
// Mutable state // Mutable state
///////////////////////// /////////////////////////
// Name of the inode, only contains relative path.
// For example, if the full path for an inode is /foo/bar/f1, its name is f1.
name string
// The current attributes of this inode. // The current attributes of this inode.
// //
// INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0 // INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
@ -78,7 +73,7 @@ type inode struct {
// Create a new inode with the supplied attributes, which need not contain // Create a new inode with the supplied attributes, which need not contain
// time-related information (the inode object will take care of that). // time-related information (the inode object will take care of that).
func newInode(attrs fuseops.InodeAttributes, name string) *inode { func newInode(attrs fuseops.InodeAttributes) *inode {
// Update time info. // Update time info.
now := time.Now() now := time.Now()
attrs.Mtime = now attrs.Mtime = now
@ -86,7 +81,6 @@ func newInode(attrs fuseops.InodeAttributes, name string) *inode {
// Create the object. // Create the object.
return &inode{ return &inode{
name: name,
attrs: attrs, attrs: attrs,
xattrs: make(map[string][]byte), xattrs: make(map[string][]byte),
} }
@ -378,8 +372,7 @@ func (in *inode) SetAttributes(
// Change mode? // Change mode?
if mode != nil { if mode != nil {
in.attrs.Mode &= ^fs.ModePerm in.attrs.Mode = *mode
in.attrs.Mode |= *mode & fs.ModePerm
} }
// Change mtime? // Change mtime?

View File

@ -16,7 +16,6 @@ package memfs
import ( import (
"context" "context"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -30,11 +29,6 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
const (
FileOpenFlagsXattrName = "fileOpenFlagsXattr"
CheckFileOpenFlagsFileName = "checkFileOpenFlags"
)
type memFS struct { type memFS struct {
fuseutil.NotImplementedFileSystem fuseutil.NotImplementedFileSystem
@ -67,9 +61,6 @@ type memFS struct {
// INVARIANT: This is all and only indices i of 'inodes' such that i > // INVARIANT: This is all and only indices i of 'inodes' such that i >
// fuseops.RootInodeID and inodes[i] == nil // fuseops.RootInodeID and inodes[i] == nil
freeInodes []fuseops.InodeID // GUARDED_BY(mu) freeInodes []fuseops.InodeID // GUARDED_BY(mu)
readFileCallback func()
writeFileCallback func()
} }
// Create a file system that stores data and metadata in memory. // Create a file system that stores data and metadata in memory.
@ -80,21 +71,11 @@ type memFS struct {
func NewMemFS( func NewMemFS(
uid uint32, uid uint32,
gid uint32) fuse.Server { gid uint32) fuse.Server {
return NewMemFSWithCallbacks(uid, gid, nil, nil)
}
func NewMemFSWithCallbacks(
uid uint32,
gid uint32,
readFileCallback func(),
writeFileCallback func()) fuse.Server {
// Set up the basic struct. // Set up the basic struct.
fs := &memFS{ fs := &memFS{
inodes: make([]*inode, fuseops.RootInodeID+1), inodes: make([]*inode, fuseops.RootInodeID+1),
uid: uid, uid: uid,
gid: gid, gid: gid,
readFileCallback: readFileCallback,
writeFileCallback: writeFileCallback,
} }
// Set up the root inode. // Set up the root inode.
@ -104,7 +85,7 @@ func NewMemFSWithCallbacks(
Gid: gid, Gid: gid,
} }
fs.inodes[fuseops.RootInodeID] = newInode(rootAttrs, "") fs.inodes[fuseops.RootInodeID] = newInode(rootAttrs)
// Set up invariant checking. // Set up invariant checking.
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants) fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
@ -176,9 +157,9 @@ func (fs *memFS) getInodeOrDie(id fuseops.InodeID) *inode {
// //
// LOCKS_REQUIRED(fs.mu) // LOCKS_REQUIRED(fs.mu)
func (fs *memFS) allocateInode( func (fs *memFS) allocateInode(
attrs fuseops.InodeAttributes, name string) (id fuseops.InodeID, inode *inode) { attrs fuseops.InodeAttributes) (id fuseops.InodeID, inode *inode) {
// Create the inode. // Create the inode.
inode = newInode(attrs, name) inode = newInode(attrs)
// Re-use a free ID if possible. Otherwise mint a new one. // Re-use a free ID if possible. Otherwise mint a new one.
numFree := len(fs.freeInodes) numFree := len(fs.freeInodes)
@ -213,6 +194,10 @@ func (fs *memFS) StatFS(
func (fs *memFS) LookUpInode( func (fs *memFS) LookUpInode(
ctx context.Context, ctx context.Context,
op *fuseops.LookUpInodeOp) error { op *fuseops.LookUpInodeOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -243,6 +228,10 @@ func (fs *memFS) LookUpInode(
func (fs *memFS) GetInodeAttributes( func (fs *memFS) GetInodeAttributes(
ctx context.Context, ctx context.Context,
op *fuseops.GetInodeAttributesOp) error { op *fuseops.GetInodeAttributesOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -262,6 +251,10 @@ func (fs *memFS) GetInodeAttributes(
func (fs *memFS) SetInodeAttributes( func (fs *memFS) SetInodeAttributes(
ctx context.Context, ctx context.Context,
op *fuseops.SetInodeAttributesOp) error { op *fuseops.SetInodeAttributesOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -291,6 +284,10 @@ func (fs *memFS) SetInodeAttributes(
func (fs *memFS) MkDir( func (fs *memFS) MkDir(
ctx context.Context, ctx context.Context,
op *fuseops.MkDirOp) error { op *fuseops.MkDirOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -313,7 +310,7 @@ func (fs *memFS) MkDir(
} }
// Allocate a child. // Allocate a child.
childID, child := fs.allocateInode(childAttrs, op.Name) childID, child := fs.allocateInode(childAttrs)
// Add an entry in the parent. // Add an entry in the parent.
parent.AddChild(childID, op.Name, fuseutil.DT_Directory) parent.AddChild(childID, op.Name, fuseutil.DT_Directory)
@ -333,6 +330,10 @@ func (fs *memFS) MkDir(
func (fs *memFS) MkNode( func (fs *memFS) MkNode(
ctx context.Context, ctx context.Context,
op *fuseops.MkNodeOp) error { op *fuseops.MkNodeOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -370,7 +371,7 @@ func (fs *memFS) createFile(
} }
// Allocate a child. // Allocate a child.
childID, child := fs.allocateInode(childAttrs, name) childID, child := fs.allocateInode(childAttrs)
// Add an entry in the parent. // Add an entry in the parent.
parent.AddChild(childID, name, fuseutil.DT_File) parent.AddChild(childID, name, fuseutil.DT_File)
@ -391,6 +392,11 @@ func (fs *memFS) createFile(
func (fs *memFS) CreateFile( func (fs *memFS) CreateFile(
ctx context.Context, ctx context.Context,
op *fuseops.CreateFileOp) (err error) { op *fuseops.CreateFileOp) (err error) {
if op.OpContext.Pid == 0 {
// CreateFileOp should have a valid pid in context.
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -401,6 +407,10 @@ func (fs *memFS) CreateFile(
func (fs *memFS) CreateSymlink( func (fs *memFS) CreateSymlink(
ctx context.Context, ctx context.Context,
op *fuseops.CreateSymlinkOp) error { op *fuseops.CreateSymlinkOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -428,7 +438,7 @@ func (fs *memFS) CreateSymlink(
} }
// Allocate a child. // Allocate a child.
childID, child := fs.allocateInode(childAttrs, op.Name) childID, child := fs.allocateInode(childAttrs)
// Set up its target. // Set up its target.
child.target = op.Target child.target = op.Target
@ -451,6 +461,10 @@ func (fs *memFS) CreateSymlink(
func (fs *memFS) CreateLink( func (fs *memFS) CreateLink(
ctx context.Context, ctx context.Context,
op *fuseops.CreateLinkOp) error { op *fuseops.CreateLinkOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -490,6 +504,10 @@ func (fs *memFS) CreateLink(
func (fs *memFS) Rename( func (fs *memFS) Rename(
ctx context.Context, ctx context.Context,
op *fuseops.RenameOp) error { op *fuseops.RenameOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -531,6 +549,10 @@ func (fs *memFS) Rename(
func (fs *memFS) RmDir( func (fs *memFS) RmDir(
ctx context.Context, ctx context.Context,
op *fuseops.RmDirOp) error { op *fuseops.RmDirOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -563,6 +585,10 @@ func (fs *memFS) RmDir(
func (fs *memFS) Unlink( func (fs *memFS) Unlink(
ctx context.Context, ctx context.Context,
op *fuseops.UnlinkOp) error { op *fuseops.UnlinkOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -590,6 +616,10 @@ func (fs *memFS) Unlink(
func (fs *memFS) OpenDir( func (fs *memFS) OpenDir(
ctx context.Context, ctx context.Context,
op *fuseops.OpenDirOp) error { op *fuseops.OpenDirOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -608,6 +638,10 @@ func (fs *memFS) OpenDir(
func (fs *memFS) ReadDir( func (fs *memFS) ReadDir(
ctx context.Context, ctx context.Context,
op *fuseops.ReadDirOp) error { op *fuseops.ReadDirOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -623,6 +657,11 @@ func (fs *memFS) ReadDir(
func (fs *memFS) OpenFile( func (fs *memFS) OpenFile(
ctx context.Context, ctx context.Context,
op *fuseops.OpenFileOp) error { op *fuseops.OpenFileOp) error {
if op.OpContext.Pid == 0 {
// OpenFileOp should have a valid pid in context.
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -635,27 +674,16 @@ func (fs *memFS) OpenFile(
panic("Found non-file.") panic("Found non-file.")
} }
if inode.name == CheckFileOpenFlagsFileName {
// For testing purpose only.
// Set attribute (name=fileOpenFlagsXattr, value=OpenFlags) to test whether
// we set OpenFlags correctly. The value is checked in test with getXattr.
value := make([]byte, 4)
binary.LittleEndian.PutUint32(value, uint32(op.OpenFlags)&syscall.O_ACCMODE)
err := fs.setXattrHelper(inode, &fuseops.SetXattrOp{
Name: FileOpenFlagsXattrName,
Value: value,
})
if err != nil {
panic("unable to set fileOpenFlagsXattr")
}
}
return nil return nil
} }
func (fs *memFS) ReadFile( func (fs *memFS) ReadFile(
ctx context.Context, ctx context.Context,
op *fuseops.ReadFileOp) error { op *fuseops.ReadFileOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -664,9 +692,8 @@ func (fs *memFS) ReadFile(
// Serve the request. // Serve the request.
var err error var err error
op.BytesRead, err = inode.ReadAt(op.Dst, op.Offset) op.Data = [][]byte{ make([]byte, op.Size) }
op.BytesRead, err = inode.ReadAt(op.Data[0], op.Offset)
op.Callback = fs.readFileCallback
// Don't return EOF errors; we just indicate EOF to fuse using a short read. // Don't return EOF errors; we just indicate EOF to fuse using a short read.
if err == io.EOF { if err == io.EOF {
@ -679,6 +706,10 @@ func (fs *memFS) ReadFile(
func (fs *memFS) WriteFile( func (fs *memFS) WriteFile(
ctx context.Context, ctx context.Context,
op *fuseops.WriteFileOp) error { op *fuseops.WriteFileOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -688,20 +719,26 @@ func (fs *memFS) WriteFile(
// Serve the request. // Serve the request.
_, err := inode.WriteAt(op.Data, op.Offset) _, err := inode.WriteAt(op.Data, op.Offset)
op.Callback = fs.writeFileCallback
return err return err
} }
func (fs *memFS) FlushFile( func (fs *memFS) FlushFile(
ctx context.Context, ctx context.Context,
op *fuseops.FlushFileOp) (err error) { op *fuseops.FlushFileOp) (err error) {
if op.OpContext.Pid == 0 {
// FlushFileOp should have a valid pid in context.
return fuse.EINVAL
}
return return
} }
func (fs *memFS) ReadSymlink( func (fs *memFS) ReadSymlink(
ctx context.Context, ctx context.Context,
op *fuseops.ReadSymlinkOp) error { op *fuseops.ReadSymlinkOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -716,6 +753,10 @@ func (fs *memFS) ReadSymlink(
func (fs *memFS) GetXattr(ctx context.Context, func (fs *memFS) GetXattr(ctx context.Context,
op *fuseops.GetXattrOp) error { op *fuseops.GetXattrOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -736,6 +777,10 @@ func (fs *memFS) GetXattr(ctx context.Context,
func (fs *memFS) ListXattr(ctx context.Context, func (fs *memFS) ListXattr(ctx context.Context,
op *fuseops.ListXattrOp) error { op *fuseops.ListXattrOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
@ -759,6 +804,10 @@ func (fs *memFS) ListXattr(ctx context.Context,
func (fs *memFS) RemoveXattr(ctx context.Context, func (fs *memFS) RemoveXattr(ctx context.Context,
op *fuseops.RemoveXattrOp) error { op *fuseops.RemoveXattrOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
inode := fs.getInodeOrDie(op.Inode) inode := fs.getInodeOrDie(op.Inode)
@ -773,15 +822,14 @@ func (fs *memFS) RemoveXattr(ctx context.Context,
func (fs *memFS) SetXattr(ctx context.Context, func (fs *memFS) SetXattr(ctx context.Context,
op *fuseops.SetXattrOp) error { op *fuseops.SetXattrOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
inode := fs.getInodeOrDie(op.Inode) inode := fs.getInodeOrDie(op.Inode)
return fs.setXattrHelper(inode, op)
}
// Required to hold fs.mu
func (fs *memFS) setXattrHelper(inode *inode, op *fuseops.SetXattrOp) error {
_, ok := inode.xattrs[op.Name] _, ok := inode.xattrs[op.Name]
switch op.Flags { switch op.Flags {
@ -803,6 +851,10 @@ func (fs *memFS) setXattrHelper(inode *inode, op *fuseops.SetXattrOp) error {
func (fs *memFS) Fallocate(ctx context.Context, func (fs *memFS) Fallocate(ctx context.Context,
op *fuseops.FallocateOp) error { op *fuseops.FallocateOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
inode := fs.getInodeOrDie(op.Inode) inode := fs.getInodeOrDie(op.Inode)

View File

@ -1,4 +1,3 @@
//go:build go1.8
// +build go1.8 // +build go1.8
package memfs_test package memfs_test

View File

@ -1,4 +1,3 @@
//go:build !go1.8
// +build !go1.8 // +build !go1.8
package memfs_test package memfs_test

View File

@ -16,7 +16,6 @@ package memfs_test
import ( import (
"bytes" "bytes"
"encoding/binary"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -32,7 +31,6 @@ import (
fallocate "github.com/detailyang/go-fallocate" fallocate "github.com/detailyang/go-fallocate"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fusetesting" "github.com/jacobsa/fuse/fusetesting"
"github.com/jacobsa/fuse/internal/fusekernel"
"github.com/jacobsa/fuse/samples" "github.com/jacobsa/fuse/samples"
"github.com/jacobsa/fuse/samples/memfs" "github.com/jacobsa/fuse/samples/memfs"
. "github.com/jacobsa/oglematchers" . "github.com/jacobsa/oglematchers"
@ -90,15 +88,6 @@ func applyUmask(m os.FileMode) os.FileMode {
return m &^ os.FileMode(umask) return m &^ os.FileMode(umask)
} }
func (t *MemFSTest) checkOpenFlagsXattr(
fileName string, expectedOpenFlags fusekernel.OpenFlags) {
dest := make([]byte, 4)
_, err := unix.Getxattr(fileName, memfs.FileOpenFlagsXattrName, dest)
AssertEq(nil, err)
openFlags := binary.LittleEndian.Uint32(dest)
AssertEq(openFlags, uint32(expectedOpenFlags))
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Boilerplate // Boilerplate
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -303,7 +292,7 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
var stat *syscall.Stat_t var stat *syscall.Stat_t
// Write a file. // Write a file.
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName) fileName := path.Join(t.Dir, "foo")
const contents = "Hello\x00world" const contents = "Hello\x00world"
createTime := time.Now() createTime := time.Now()
@ -315,7 +304,7 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
stat = fi.Sys().(*syscall.Stat_t) stat = fi.Sys().(*syscall.Stat_t)
AssertEq(nil, err) AssertEq(nil, err)
ExpectEq(memfs.CheckFileOpenFlagsFileName, fi.Name()) ExpectEq("foo", fi.Name())
ExpectEq(len(contents), fi.Size()) ExpectEq(len(contents), fi.Size())
ExpectEq(applyUmask(0400), fi.Mode()) ExpectEq(applyUmask(0400), fi.Mode())
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop)) ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
@ -332,7 +321,6 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
slice, err := ioutil.ReadFile(fileName) slice, err := ioutil.ReadFile(fileName)
AssertEq(nil, err) AssertEq(nil, err)
ExpectEq(contents, string(slice)) ExpectEq(contents, string(slice))
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
} }
func (t *MemFSTest) CreateNewFile_InSubDir() { func (t *MemFSTest) CreateNewFile_InSubDir() {
@ -384,7 +372,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
var stat *syscall.Stat_t var stat *syscall.Stat_t
// Write a file. // Write a file.
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName) fileName := path.Join(t.Dir, "foo")
createTime := time.Now() createTime := time.Now()
err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600) err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600)
@ -394,7 +382,6 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400) f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
t.ToClose = append(t.ToClose, f) t.ToClose = append(t.ToClose, f)
AssertEq(nil, err) AssertEq(nil, err)
t.checkOpenFlagsXattr(fileName, fusekernel.OpenWriteOnly)
modifyTime := time.Now() modifyTime := time.Now()
n, err = f.WriteAt([]byte("H"), 0) n, err = f.WriteAt([]byte("H"), 0)
@ -406,7 +393,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
stat = fi.Sys().(*syscall.Stat_t) stat = fi.Sys().(*syscall.Stat_t)
AssertEq(nil, err) AssertEq(nil, err)
ExpectEq(memfs.CheckFileOpenFlagsFileName, fi.Name()) ExpectEq("foo", fi.Name())
ExpectEq(len("Hello, world!"), fi.Size()) ExpectEq(len("Hello, world!"), fi.Size())
ExpectEq(applyUmask(0600), fi.Mode()) ExpectEq(applyUmask(0600), fi.Mode())
ExpectThat(fi, fusetesting.MtimeIsWithin(modifyTime, timeSlop)) ExpectThat(fi, fusetesting.MtimeIsWithin(modifyTime, timeSlop))
@ -423,7 +410,6 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
slice, err := ioutil.ReadFile(fileName) slice, err := ioutil.ReadFile(fileName)
AssertEq(nil, err) AssertEq(nil, err)
ExpectEq("Hello, world!", string(slice)) ExpectEq("Hello, world!", string(slice))
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
} }
func (t *MemFSTest) ModifyExistingFile_InSubDir() { func (t *MemFSTest) ModifyExistingFile_InSubDir() {
@ -846,7 +832,7 @@ func (t *MemFSTest) AppendMode() {
buf := make([]byte, 1024) buf := make([]byte, 1024)
// Create a file with some contents. // Create a file with some contents.
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName) fileName := path.Join(t.Dir, "foo")
err = ioutil.WriteFile(fileName, []byte("Jello, "), 0600) err = ioutil.WriteFile(fileName, []byte("Jello, "), 0600)
AssertEq(nil, err) AssertEq(nil, err)
@ -854,7 +840,6 @@ func (t *MemFSTest) AppendMode() {
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0600) f, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0600)
t.ToClose = append(t.ToClose, f) t.ToClose = append(t.ToClose, f)
AssertEq(nil, err) AssertEq(nil, err)
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadWrite)
// Seek to somewhere silly and then write. // Seek to somewhere silly and then write.
off, err = f.Seek(2, 0) off, err = f.Seek(2, 0)
@ -922,7 +907,7 @@ func (t *MemFSTest) ReadsPastEndOfFile() {
func (t *MemFSTest) Truncate_Smaller() { func (t *MemFSTest) Truncate_Smaller() {
var err error var err error
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName) fileName := path.Join(t.Dir, "foo")
// Create a file. // Create a file.
err = ioutil.WriteFile(fileName, []byte("taco"), 0600) err = ioutil.WriteFile(fileName, []byte("taco"), 0600)
@ -932,7 +917,6 @@ func (t *MemFSTest) Truncate_Smaller() {
f, err := os.OpenFile(fileName, os.O_RDWR, 0) f, err := os.OpenFile(fileName, os.O_RDWR, 0)
t.ToClose = append(t.ToClose, f) t.ToClose = append(t.ToClose, f)
AssertEq(nil, err) AssertEq(nil, err)
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadWrite)
// Truncate it. // Truncate it.
err = f.Truncate(2) err = f.Truncate(2)
@ -947,7 +931,6 @@ func (t *MemFSTest) Truncate_Smaller() {
contents, err := ioutil.ReadFile(fileName) contents, err := ioutil.ReadFile(fileName)
AssertEq(nil, err) AssertEq(nil, err)
ExpectEq("ta", string(contents)) ExpectEq("ta", string(contents))
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
} }
func (t *MemFSTest) Truncate_SameSize() { func (t *MemFSTest) Truncate_SameSize() {

View File

@ -50,7 +50,6 @@ func main() {
cfg := &fuse.MountConfig{ cfg := &fuse.MountConfig{
ReadOnly: *fReadOnly, ReadOnly: *fReadOnly,
FuseImpl: fuse.FUSEImplMacFUSE,
} }
if *fDebug { if *fDebug {

View File

@ -1,68 +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 main
import (
"context"
"flag"
"log"
"os/user"
"strconv"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/samples/memfs"
)
var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
func main() {
flag.Parse()
if *fMountPoint == "" {
log.Fatalf("You must set --mount_point.")
}
user, err := user.Current()
if err != nil {
panic(err)
}
uid, err := strconv.ParseUint(user.Uid, 10, 32)
if err != nil {
panic(err)
}
gid, err := strconv.ParseUint(user.Gid, 10, 32)
if err != nil {
panic(err)
}
server := memfs.NewMemFS(uint32(uid), uint32(gid))
cfg := &fuse.MountConfig{
// Disable writeback caching so that pid is always available in OpContext
DisableWritebackCaching: true,
}
mfs, err := fuse.Mount(*fMountPoint, server, cfg)
if err != nil {
log.Fatalf("Mount: %v", err)
}
// Wait for it to be unmounted.
if err = mfs.Join(context.Background()); err != nil {
log.Fatalf("Join: %v", err)
}
}

View File

@ -3,12 +3,9 @@ package main
import ( import (
"context" "context"
"flag" "flag"
"fmt"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/samples/readbenchfs" "github.com/jacobsa/fuse/samples/readbenchfs"
"log" "log"
"net/http"
_ "net/http/pprof"
"os" "os"
) )
@ -16,18 +13,11 @@ var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
var fReadOnly = flag.Bool("read_only", false, "Mount in read-only mode.") var fReadOnly = flag.Bool("read_only", false, "Mount in read-only mode.")
var fVectored = flag.Bool("vectored", false, "Use vectored read.") var fVectored = flag.Bool("vectored", false, "Use vectored read.")
var fDebug = flag.Bool("debug", false, "Enable debug logging.") var fDebug = flag.Bool("debug", false, "Enable debug logging.")
var fPprof = flag.Int("pprof", 0, "Enable pprof profiling on the specified port.")
func main() { func main() {
flag.Parse() flag.Parse()
if *fPprof != 0 { server, err := readbenchfs.NewReadBenchServer()
go func() {
fmt.Printf("%v", http.ListenAndServe(fmt.Sprintf("localhost:%v", *fPprof), nil))
}()
}
server, err := readbenchfs.NewReadBenchServer(*fVectored)
if err != nil { if err != nil {
log.Fatalf("makeFS: %v", err) log.Fatalf("makeFS: %v", err)
} }

View File

@ -27,8 +27,7 @@ import (
type readBenchFS struct { type readBenchFS struct {
fuseutil.NotImplementedFileSystem fuseutil.NotImplementedFileSystem
buf []byte buf []byte
useVectoredRead bool
} }
// 1 TB // 1 TB
@ -36,13 +35,12 @@ const fileSize = 1024 * 1024 * 1024 * 1024
var _ fuseutil.FileSystem = &readBenchFS{} var _ fuseutil.FileSystem = &readBenchFS{}
func NewReadBenchServer(useVectoredRead bool) (server fuse.Server, err error) { func NewReadBenchServer() (server fuse.Server, err error) {
// 1 GB of random data to exceed CPU cache // 1 GB of random data to exceed CPU cache
buf := make([]byte, 1024*1024*1024) buf := make([]byte, 1024*1024*1024)
rand.Read(buf) rand.Read(buf)
server = fuseutil.NewFileSystemServer(&readBenchFS{ server = fuseutil.NewFileSystemServer(&readBenchFS{
buf: buf, buf: buf,
useVectoredRead: useVectoredRead,
}) })
return return
} }
@ -120,6 +118,28 @@ func (fs *readBenchFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) err
} }
func (fs *readBenchFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) error { func (fs *readBenchFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) error {
if op.Offset > fileSize {
return io.EOF
}
end := op.Offset + int64(len(op.Dst))
if end > fileSize {
end = fileSize
}
buflen := int64(len(fs.buf))
for pos := op.Offset; pos < end; {
s := pos % buflen
e := buflen
if e-s > end-pos {
e = s + end - pos
}
copy(op.Dst[pos-op.Offset:], fs.buf[s:])
pos = op.Offset + e
}
op.BytesRead = int(end - op.Offset)
return nil
}
func (fs *readBenchFS) VectoredRead(ctx context.Context, op *fuseops.VectoredReadOp) error {
if op.Offset > fileSize { if op.Offset > fileSize {
return io.EOF return io.EOF
} }
@ -134,11 +154,7 @@ func (fs *readBenchFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) err
if e-s > end-pos { if e-s > end-pos {
e = s + end - pos e = s + end - pos
} }
if fs.useVectoredRead { op.Data = append(op.Data, fs.buf[s:e])
op.Data = append(op.Data, fs.buf[s:e])
} else {
copy(op.Dst[pos-op.Offset:], fs.buf[s:])
}
pos = op.Offset + e pos = op.Offset + e
} }
op.BytesRead = int(end - op.Offset) op.BytesRead = int(end - op.Offset)

View File

@ -21,7 +21,7 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"sync/atomic" "sync/atomic"
"syscall" "time"
"github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/fuse/fuseutil"
@ -53,20 +53,21 @@ func getOrCreateInode(inodes *sync.Map, parentId fuseops.InodeID, name string) (
return nil, nil return nil, nil
} }
parentPath := parent.(Inode).Path() parentPath := parent.(Inode).Path()
entries, err := ioutil.ReadDir(parentPath)
path := filepath.Join(parentPath, name)
fileInfo, err := os.Stat(path)
if err != nil { if err != nil {
return nil, nil return nil, err
} }
stat, _ := fileInfo.Sys().(*syscall.Stat_t) for _, entry := range entries {
if entry.Name() == name {
inodeEntry := &inodeEntry{ inodeEntry := &inodeEntry{
id: fuseops.InodeID(stat.Ino), id: nextInodeID(),
path: path, path: filepath.Join(parentPath, name),
}
storedEntry, _ := inodes.LoadOrStore(inodeEntry.id, inodeEntry)
return storedEntry.(Inode), nil
}
} }
storedEntry, _ := inodes.LoadOrStore(inodeEntry.id, inodeEntry) return nil, nil
return storedEntry.(Inode), nil
} }
type inodeEntry struct { type inodeEntry struct {
@ -100,14 +101,16 @@ func (in *inodeEntry) Attributes() (*fuseops.InodeAttributes, error) {
if err != nil { if err != nil {
return &fuseops.InodeAttributes{}, err return &fuseops.InodeAttributes{}, err
} }
return &fuseops.InodeAttributes{ return &fuseops.InodeAttributes{
Size: uint64(fileInfo.Size()), Size: uint64(fileInfo.Size()),
Nlink: 1, Nlink: 1,
Mode: fileInfo.Mode(), Mode: fileInfo.Mode(),
Mtime: fileInfo.ModTime(), Atime: fileInfo.ModTime(),
Uid: uid, Mtime: fileInfo.ModTime(),
Gid: gid, Ctime: time.Now(),
Crtime: time.Now(),
Uid: uid,
Gid: gid,
}, nil }, nil
} }
@ -116,12 +119,12 @@ func (in *inodeEntry) ListChildren(inodes *sync.Map) ([]*fuseutil.Dirent, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dirents := []*fuseutil.Dirent{} dirents := make([]*fuseutil.Dirent, len(children))
for i, child := range children { for i, child := range children {
childInode, err := getOrCreateInode(inodes, in.id, child.Name()) childInode, err := getOrCreateInode(inodes, in.id, child.Name())
if err != nil || childInode == nil { if err != nil || childInode == nil {
continue return nil, nil
} }
var childType fuseutil.DirentType var childType fuseutil.DirentType
@ -133,12 +136,12 @@ func (in *inodeEntry) ListChildren(inodes *sync.Map) ([]*fuseutil.Dirent, error)
childType = fuseutil.DT_File childType = fuseutil.DT_File
} }
dirents = append(dirents, &fuseutil.Dirent{ dirents[i] = &fuseutil.Dirent{
Offset: fuseops.DirOffset(i + 1), Offset: fuseops.DirOffset(i + 1),
Inode: childInode.Id(), Inode: childInode.Id(),
Name: child.Name(), Name: child.Name(),
Type: childType, Type: childType,
}) }
} }
return dirents, nil return dirents, nil
} }

View File

@ -121,7 +121,7 @@ func (fs *readonlyLoopbackFs) ReadDir(
} }
if op.Offset > fuseops.DirOffset(len(children)) { if op.Offset > fuseops.DirOffset(len(children)) {
return nil return fuse.EIO
} }
children = children[op.Offset:] children = children[op.Offset:]
@ -139,13 +139,8 @@ func (fs *readonlyLoopbackFs) ReadDir(
func (fs *readonlyLoopbackFs) OpenFile( func (fs *readonlyLoopbackFs) OpenFile(
ctx context.Context, ctx context.Context,
op *fuseops.OpenFileOp) error { op *fuseops.OpenFileOp) error {
// Allow opening any file.
var _, found = fs.inodes.Load(op.Inode)
if !found {
return fuse.ENOENT
}
return nil return nil
} }
func (fs *readonlyLoopbackFs) ReadFile( func (fs *readonlyLoopbackFs) ReadFile(
@ -165,8 +160,13 @@ func (fs *readonlyLoopbackFs) ReadFile(
return fuse.EIO return fuse.EIO
} }
contents = contents[op.Offset:] end := op.Offset+op.Size
op.BytesRead = copy(op.Dst, contents) if end > int64(len(contents)) {
end = int64(len(contents))
}
op.Data = [][]byte{ contents[op.Offset : end] }
op.BytesRead = int(end-op.Offset)
return nil return nil
} }

View File

@ -16,7 +16,6 @@ package statfs_test
import ( import (
"fmt" "fmt"
"github.com/jacobsa/oglematchers"
"math" "math"
"regexp" "regexp"
"syscall" "syscall"
@ -27,8 +26,9 @@ import (
// Sample output: // Sample output:
// //
// Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on // Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
// fake@bucket 32 16 16 50% 0 0 100% /Users/jacobsa/tmp/mp // fake@bucket 32 16 16 50% 0 0 100% /Users/jacobsa/tmp/mp
//
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+\d+\s+\d+\s+\d+%.*$`) var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+\d+\s+\d+\s+\d+%.*$`)
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -69,7 +69,7 @@ func (t *StatFSTest) Syscall_ZeroValues() {
ExpectEq(0, stat.Bavail) ExpectEq(0, stat.Bavail)
ExpectEq(0, stat.Files) ExpectEq(0, stat.Files)
ExpectEq(0, stat.Ffree) ExpectEq(0, stat.Ffree)
ExpectThat(convertName(stat.Fstypename[:]), oglematchers.AnyOf(oglematchers.Equals("osxfuse"), oglematchers.Equals("macfuse"))) ExpectEq("osxfuse", convertName(stat.Fstypename[:]))
ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:])) ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
ExpectEq(fsName, convertName(stat.Mntfromname[:])) ExpectEq(fsName, convertName(stat.Mntfromname[:]))
} }
@ -104,7 +104,7 @@ func (t *StatFSTest) Syscall_NonZeroValues() {
ExpectEq(canned.BlocksAvailable, stat.Bavail) ExpectEq(canned.BlocksAvailable, stat.Bavail)
ExpectEq(canned.Inodes, stat.Files) ExpectEq(canned.Inodes, stat.Files)
ExpectEq(canned.InodesFree, stat.Ffree) ExpectEq(canned.InodesFree, stat.Ffree)
ExpectThat(convertName(stat.Fstypename[:]), oglematchers.AnyOf(oglematchers.Equals("osxfuse"), oglematchers.Equals("macfuse"))) ExpectEq("osxfuse", convertName(stat.Fstypename[:]))
ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:])) ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
ExpectEq(fsName, convertName(stat.Mntfromname[:])) ExpectEq(fsName, convertName(stat.Mntfromname[:]))
} }

View File

@ -26,8 +26,9 @@ import (
// Sample output: // Sample output:
// //
// Filesystem 1K-blocks Used Available Use% Mounted on // Filesystem 1K-blocks Used Available Use% Mounted on
// some_fuse_file_system 512 64 384 15% /tmp/sample_test001288095 // some_fuse_file_system 512 64 384 15% /tmp/sample_test001288095
//
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%.*$`) var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%.*$`)
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////

View File

@ -1,4 +1,3 @@
//go:build !linux
// +build !linux // +build !linux
package fuse package fuse