Compare commits

..

3 Commits

31 changed files with 395 additions and 1362 deletions

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.
@ -386,11 +374,6 @@ func (c *Connection) writeMessage(outMsg *buffer.OutMessage) error {
var n int var n int
expectedLen := outMsg.Len() expectedLen := outMsg.Len()
if outMsg.Sglist != nil { 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) n, err = writev(int(c.dev.Fd()), outMsg.Sglist)
} else { } else {
// Avoid the retry loop in os.File.Write. // Avoid the retry loop in os.File.Write.
@ -399,12 +382,8 @@ func (c *Connection) writeMessage(outMsg *buffer.OutMessage) error {
if err == nil && n != expectedLen { if err == nil && n != expectedLen {
err = fmt.Errorf("Wrote %d bytes; expected %d", n, expectedLen) err = fmt.Errorf("Wrote %d bytes; expected %d", n, expectedLen)
} }
if err != nil { if err != nil && c.errorLogger != nil {
writeErrMsg := fmt.Sprintf("writeMessage: %v %v", err, outMsg.OutHeaderBytes()) c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.OutHeaderBytes())
if c.errorLogger != nil {
c.errorLogger.Print(writeErrMsg)
}
return fmt.Errorf(writeErrMsg)
} }
outMsg.Sglist = nil outMsg.Sglist = nil
return err return err
@ -481,7 +460,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 +473,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 +491,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 +501,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())
} }
@ -557,18 +518,6 @@ func (c *Connection) Reply(ctx context.Context, opErr error) error {
if !noResponse { if !noResponse {
c.writeMessage(outMsg) c.writeMessage(outMsg)
} }
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 // Send a notification to the kernel

View File

@ -56,8 +56,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -67,8 +65,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -84,8 +80,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
o = to o = to
@ -104,7 +98,7 @@ func convertInMessage(
} }
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
} }
@ -136,8 +130,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -167,8 +159,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -190,17 +180,15 @@ 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{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -220,13 +208,10 @@ func convertInMessage(
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{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -246,12 +231,10 @@ func convertInMessage(
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{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -274,8 +257,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -321,8 +302,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -339,8 +318,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -357,8 +334,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -375,8 +350,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -386,8 +359,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -405,8 +376,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
if !config.UseVectoredRead { if !config.UseVectoredRead {
@ -416,8 +385,6 @@ func convertInMessage(
} }
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 {
@ -428,12 +395,9 @@ func convertInMessage(
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{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
o = to o = to
@ -461,8 +425,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -478,8 +440,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -502,8 +462,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -511,7 +469,7 @@ func convertInMessage(
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{
@ -520,23 +478,9 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, 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),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
case fusekernel.OpFlush: case fusekernel.OpFlush:
type input fusekernel.FlushIn type input fusekernel.FlushIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{}))) in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
@ -550,8 +494,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -561,8 +503,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -617,8 +557,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -635,8 +573,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
@ -660,8 +596,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
o = to o = to
@ -693,8 +627,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid,
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
}, },
} }
o = to o = to
@ -736,10 +668,7 @@ func convertInMessage(
Flags: in.Flags, Flags: in.Flags,
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, 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
@ -756,10 +685,7 @@ func convertInMessage(
Mode: in.Mode, Mode: in.Mode,
OpContext: fuseops.OpContext{ OpContext: fuseops.OpContext{
FuseID: inMsg.Header().Unique, FuseID: inMsg.Header().Unique,
Pid: inMsg.Header().Pid, Pid: inMsg.Header().Pid},
Uid: inMsg.Header().Uid,
Gid: inMsg.Header().Gid,
},
} }
case fusekernel.OpPoll: case fusekernel.OpPoll:
@ -877,37 +803,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 +841,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 +861,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
@ -997,7 +915,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 +925,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 +969,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{}))))
@ -1149,6 +1064,120 @@ func (c *Connection) kernelNotification(
// 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
}
if in.Mode&os.ModeSetgid != 0 {
out.Mode |= syscall.S_ISGID
}
if in.Mode&os.ModeSticky != 0 {
out.Mode |= syscall.S_ISVTX
}
}
// 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
}
if unixMode&syscall.S_ISVTX != 0 {
mode |= os.ModeSticky
}
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

@ -115,9 +115,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 +141,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, ", "))
} }

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

@ -34,29 +34,22 @@ type OpContext struct {
// 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 +60,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
@ -193,22 +186,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 +207,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.
@ -272,11 +262,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 +286,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 +304,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 +316,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,8 +397,8 @@ 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
@ -421,7 +407,7 @@ type CreateLinkOp struct {
// //
// - 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
@ -429,9 +415,9 @@ type CreateLinkOp struct {
// //
// - 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
@ -460,7 +446,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 +460,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 +493,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 +512,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 +562,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 +580,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 +595,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 +628,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
@ -718,21 +685,15 @@ type ReadFileOp struct {
// 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.
@ -742,22 +703,21 @@ type ReadFileOp struct {
// 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 +749,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 +782,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 +841,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
@ -1026,11 +966,6 @@ type FallocateOp struct {
OpContext OpContext OpContext OpContext
} }
type SyncFSOp struct {
Inode InodeID
OpContext OpContext
}
// Request notifications when the file system user calls poll/select or // Request notifications when the file system user calls poll/select or
// similar operations on a file. // similar operations on a file.
type PollOp struct { type PollOp struct {

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,15 @@ 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 +157,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 +166,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 +202,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
@ -52,21 +51,13 @@ type Dirent struct {
} }
// Write the supplied directory entry into the given buffer in the format // Write the supplied directory entry into 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

@ -63,7 +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 Poll(context.Context, *fuseops.PollOp) error
SetConnection(*fuse.Connection) SetConnection(*fuse.Connection)
@ -85,8 +84,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,
@ -243,9 +242,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: case *fuseops.PollOp:
err = s.fs.Poll(ctx, typed) err = s.fs.Poll(ctx, typed)
} }

View File

@ -204,12 +204,6 @@ 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( func (fs *NotImplementedFileSystem) Poll(
ctx context.Context, ctx context.Context,
op *fuseops.PollOp) error { op *fuseops.PollOp) error {

6
go.mod
View File

@ -1,6 +1,6 @@
module github.com/jacobsa/fuse module github.com/jacobsa/fuse
go 1.20 go 1.19
require ( require (
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e
@ -9,8 +9,8 @@ require (
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.7.0
golang.org/x/sys v0.18.0 golang.org/x/sys v0.5.0
) )
require ( require (

8
go.sum
View File

@ -14,7 +14,7 @@ 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/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

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

@ -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
@ -422,14 +417,6 @@ const (
OpNotifyReply = 41 OpNotifyReply = 41
OpBatchForget = 42 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
@ -862,10 +849,6 @@ type NotifyInvalEntryOut struct {
padding uint32 padding uint32
} }
type SyncFSIn struct {
Padding uint64
}
type NotifyDeleteOut struct { type NotifyDeleteOut struct {
Parent uint64 Parent uint64
Child uint64 Child 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

@ -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,11 +151,6 @@ 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.
@ -172,16 +166,9 @@ type MountConfig struct {
// 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 +185,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 = "/"
@ -216,7 +212,7 @@ func callMountCommFD(
// 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

@ -84,7 +84,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 {
@ -97,11 +96,11 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
cfg.DebugLogger.Println("Starting the unix mounting") 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

View File

@ -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,14 +66,6 @@ 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
@ -139,10 +126,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 +202,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 +254,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 +349,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
} }

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

@ -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:]

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"
@ -378,8 +377,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

@ -67,9 +67,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 +77,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.
@ -640,7 +627,7 @@ func (fs *memFS) OpenFile(
// Set attribute (name=fileOpenFlagsXattr, value=OpenFlags) to test whether // Set attribute (name=fileOpenFlagsXattr, value=OpenFlags) to test whether
// we set OpenFlags correctly. The value is checked in test with getXattr. // we set OpenFlags correctly. The value is checked in test with getXattr.
value := make([]byte, 4) value := make([]byte, 4)
binary.LittleEndian.PutUint32(value, uint32(op.OpenFlags)&syscall.O_ACCMODE) binary.LittleEndian.PutUint32(value, uint32(op.OpenFlags))
err := fs.setXattrHelper(inode, &fuseops.SetXattrOp{ err := fs.setXattrHelper(inode, &fuseops.SetXattrOp{
Name: FileOpenFlagsXattrName, Name: FileOpenFlagsXattrName,
Value: value, Value: value,
@ -666,8 +653,6 @@ func (fs *memFS) ReadFile(
var err error var err error
op.BytesRead, err = inode.ReadAt(op.Dst, op.Offset) op.BytesRead, err = inode.ReadAt(op.Dst, 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 {
return nil return nil
@ -688,8 +673,6 @@ 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
} }

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

@ -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(