Skip to content

Commit 299578a

Browse files
committed
Add notification ops and conversions
1 parent 5231ede commit 299578a

5 files changed

Lines changed: 232 additions & 29 deletions

File tree

connection.go

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -375,18 +375,33 @@ func (c *Connection) readMessage() (*buffer.InMessage, error) {
375375
}
376376

377377
// Write the supplied message to the kernel.
378-
func (c *Connection) writeMessage(msg []byte) error {
379-
// Avoid the retry loop in os.File.Write.
380-
n, err := syscall.Write(int(c.dev.Fd()), msg)
381-
if err != nil {
382-
return err
378+
func (c *Connection) writeMessage(outMsg *buffer.OutMessage) error {
379+
var err error
380+
var n int
381+
expectedLen := outMsg.Len()
382+
if outMsg.Sglist != nil {
383+
if fusekernel.IsPlatformFuseT {
384+
// writev is not atomic on macos, restrict to fuse-t platform
385+
writeLock.Lock()
386+
defer writeLock.Unlock()
387+
}
388+
n, err = writev(int(c.dev.Fd()), outMsg.Sglist)
389+
} else {
390+
// Avoid the retry loop in os.File.Write.
391+
n, err = syscall.Write(int(c.dev.Fd()), outMsg.OutHeaderBytes())
383392
}
384-
385-
if n != len(msg) {
386-
return fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg))
393+
if err == nil && n != expectedLen {
394+
err = fmt.Errorf("Wrote %d bytes; expected %d", n, expectedLen)
387395
}
388-
389-
return nil
396+
if err != nil {
397+
writeErrMsg := fmt.Sprintf("writeMessage: %v %v", err, outMsg.OutHeaderBytes())
398+
if c.errorLogger != nil {
399+
c.errorLogger.Print(writeErrMsg)
400+
}
401+
return fmt.Errorf(writeErrMsg)
402+
}
403+
outMsg.Sglist = nil
404+
return err
390405
}
391406

392407
// ReadOp consumes the next op from the kernel process, returning the op and a
@@ -527,25 +542,7 @@ func (c *Connection) Reply(ctx context.Context, opErr error) error {
527542
noResponse := c.kernelResponse(outMsg, inMsg.Header().Unique, op, opErr)
528543

529544
if !noResponse {
530-
var err error
531-
if outMsg.Sglist != nil {
532-
if fusekernel.IsPlatformFuseT {
533-
// writev is not atomic on macos, restrict to fuse-t platform
534-
writeLock.Lock()
535-
defer writeLock.Unlock()
536-
}
537-
_, err = writev(int(c.dev.Fd()), outMsg.Sglist)
538-
} else {
539-
err = c.writeMessage(outMsg.OutHeaderBytes())
540-
}
541-
if err != nil {
542-
writeErrMsg := fmt.Sprintf("writeMessage: %v %v", err, outMsg.OutHeaderBytes())
543-
if c.errorLogger != nil {
544-
c.errorLogger.Print(writeErrMsg)
545-
}
546-
return fmt.Errorf(writeErrMsg)
547-
}
548-
outMsg.Sglist = nil
545+
c.writeMessage(outMsg)
549546
}
550547

551548
return nil
@@ -561,6 +558,17 @@ func (c *Connection) callbackForOp(op interface{}) func() {
561558
return nil
562559
}
563560

561+
// Send a notification to the kernel
562+
// notification must be a pointer to one of fuseops.NotifyXXX structures
563+
// To avoid a deadlock notifications must not be called in the execution path of a related filesytem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.
564+
func (c *Connection) Notify(notification interface{}) error {
565+
outMsg := c.getOutMessage()
566+
defer c.putOutMessage(outMsg)
567+
c.kernelNotification(outMsg, notification)
568+
outMsg.OutHeader().Len = uint32(outMsg.Len())
569+
return c.writeMessage(outMsg)
570+
}
571+
564572
// Close the connection. Must not be called until operations that were read
565573
// from the connection have been responded to.
566574
func (c *Connection) close() error {

conversions.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,26 @@ func convertInMessage(
747747
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
748748
}
749749

750+
case fusekernel.OpNotifyReply:
751+
type input fusekernel.NotifyRetrieveIn
752+
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
753+
if in == nil {
754+
return nil, errors.New("Corrupt OpNotifyReply")
755+
}
756+
757+
buf := inMsg.ConsumeBytes(inMsg.Len())
758+
if len(buf) < int(in.Size) {
759+
return nil, errors.New("Corrupt OpNotifyReply")
760+
}
761+
762+
o = &fuseops.NotifyRetrieveReplyOp{
763+
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
764+
Unique: inMsg.Header().Unique,
765+
Offset: in.Offset,
766+
Length: in.Size,
767+
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
768+
}
769+
750770
default:
751771
o = &unknownOp{
752772
OpCode: inMsg.Header().Opcode,
@@ -780,6 +800,9 @@ func (c *Connection) kernelResponse(
780800
case *fuseops.BatchForgetOp:
781801
return true
782802

803+
case *fuseops.NotifyRetrieveReplyOp:
804+
return true
805+
783806
case *interruptOp:
784807
return true
785808
}
@@ -1018,13 +1041,79 @@ func (c *Connection) kernelResponseForOp(
10181041
out := (*fusekernel.PollOut)(m.Grow(int(unsafe.Sizeof(fusekernel.PollOut{}))))
10191042
out.Revents = uint32(o.Revents)
10201043

1044+
case *fuseops.NotifyRetrieveReplyOp:
1045+
// Empty response
1046+
10211047
default:
10221048
panic(fmt.Sprintf("Unexpected op: %#v", op))
10231049
}
10241050

10251051
return
10261052
}
10271053

1054+
// Like kernelResponse, but assumes the user replied with a nil error to the op.
1055+
func (c *Connection) kernelNotification(
1056+
m *buffer.OutMessage,
1057+
op interface{}) {
1058+
1059+
h := m.OutHeader()
1060+
h.Unique = 0
1061+
1062+
// Create the appropriate output message
1063+
switch o := op.(type) {
1064+
case *fuseops.NotifyPollWakeup:
1065+
h.Error = fusekernel.NotifyCodePoll
1066+
out := (*fusekernel.NotifyPollWakeupOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyPollWakeupOut{}))))
1067+
out.Kh = o.Kh
1068+
1069+
case *fuseops.NotifyInvalInode:
1070+
h.Error = fusekernel.NotifyCodeInvalInode
1071+
out := (*fusekernel.NotifyInvalInodeOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyInvalInodeOut{}))))
1072+
out.Ino = uint64(o.Inode)
1073+
out.Off = o.Offset
1074+
out.Len = o.Length
1075+
1076+
case *fuseops.NotifyInvalEntry:
1077+
h.Error = fusekernel.NotifyCodeInvalEntry
1078+
out := (*fusekernel.NotifyInvalEntryOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyInvalEntryOut{}))))
1079+
out.Parent = uint64(o.Parent)
1080+
out.Namelen = uint32(len(o.Name))
1081+
m.AppendString(o.Name)
1082+
m.AppendString("\x00")
1083+
1084+
case *fuseops.NotifyDelete:
1085+
h.Error = fusekernel.NotifyCodeDelete
1086+
out := (*fusekernel.NotifyDeleteOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyDeleteOut{}))))
1087+
out.Parent = uint64(o.Parent)
1088+
out.Child = uint64(o.Child)
1089+
out.Namelen = uint32(len(o.Name))
1090+
m.AppendString(o.Name)
1091+
m.AppendString("\x00")
1092+
1093+
case *fuseops.NotifyStore:
1094+
h.Error = fusekernel.NotifyCodeStore
1095+
out := (*fusekernel.NotifyStoreOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyStoreOut{}))))
1096+
out.Nodeid = uint64(o.Inode)
1097+
out.Offset = o.Offset
1098+
out.Size = o.Length
1099+
m.Append(o.Data...)
1100+
m.ShrinkTo(buffer.OutMessageHeaderSize + int(unsafe.Sizeof(fusekernel.NotifyStoreOut{})) + int(o.Length))
1101+
1102+
case *fuseops.NotifyRetrieve:
1103+
h.Error = fusekernel.NotifyCodeRetrieve
1104+
out := (*fusekernel.NotifyRetrieveOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyRetrieveOut{}))))
1105+
out.Unique = o.Unique
1106+
out.Nodeid = uint64(o.Inode)
1107+
out.Offset = o.Offset
1108+
out.Size = o.Length
1109+
1110+
default:
1111+
panic(fmt.Sprintf("Unexpected notification: %#v", op))
1112+
}
1113+
1114+
return
1115+
}
1116+
10281117
////////////////////////////////////////////////////////////////////////
10291118
// General conversions
10301119
////////////////////////////////////////////////////////////////////////

fuseops/ops.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,3 +1034,102 @@ type PollOp struct {
10341034
Revents fusekernel.PollEvents
10351035
OpContext OpContext
10361036
}
1037+
1038+
// Notify consumers waiting for poll/epoll that events are incoming
1039+
// for the specified kernel handle. The kernel will send a PollOp request
1040+
// to get the event mask after receiving this notification
1041+
type NotifyPollWakeup struct {
1042+
Kh uint64
1043+
}
1044+
1045+
// Notify to invalidate cache for an inode.
1046+
//
1047+
// If the filesystem has writeback caching enabled, invalidating an inode
1048+
// will first trigger a writeback of all dirty pages. The call will block
1049+
// until all writeback requests have completed and the inode has been
1050+
// invalidated. It will, however, not wait for completion of pending writeback
1051+
// requests that have been issued before.
1052+
type NotifyInvalInode struct {
1053+
Inode InodeID
1054+
Offset int64
1055+
Length int64
1056+
}
1057+
1058+
// Notify to invalidate parent attributes and the dentry matching parent/name
1059+
//
1060+
// To avoid a deadlock this request must not be sent in the execution path
1061+
// of a related filesytem operation or within any code that could hold a lock
1062+
// that could be needed to execute such an operation. As of kernel 4.18, a
1063+
// "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(),
1064+
// rename(), link() or create() request for the parent, and a setattr(),
1065+
// unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or
1066+
// readdirplus() request for the inode itself.
1067+
//
1068+
// When called correctly, it will never block.
1069+
type NotifyInvalEntry struct {
1070+
Parent InodeID
1071+
Name string
1072+
}
1073+
1074+
// This request behaves like NotifyInvalEntry with the following additional
1075+
// effect (at least as of Linux kernel 4.8):
1076+
//
1077+
// If the provided child inode matches the inode that is currently associated
1078+
// with the cached dentry, and if there are any inotify watches registered for
1079+
// the dentry, then the watchers are informed that the dentry has been deleted.
1080+
//
1081+
// To avoid a deadlock this request must not be sent while executing a
1082+
// related filesytem operation or while holding a lock that could be needed to
1083+
// execute such an operation.
1084+
type NotifyDelete struct {
1085+
Parent InodeID
1086+
Child InodeID
1087+
Name string
1088+
}
1089+
1090+
// Store data to the kernel buffers
1091+
//
1092+
// Synchronously store data in the kernel buffers belonging to the given inode.
1093+
// The stored data is marked up-to-date (no read will be performed against it,
1094+
// unless it's invalidated or evicted from the cache).
1095+
//
1096+
// If the stored data overflows the current file size, then the size is extended,
1097+
// similarly to a write(2) on the filesystem.
1098+
//
1099+
// If this request returns an error, then the store wasn't fully completed, but
1100+
// it may have been partially completed.
1101+
type NotifyStore struct {
1102+
Inode InodeID
1103+
Offset uint64
1104+
Length uint32
1105+
Data [][]byte
1106+
}
1107+
1108+
// Retrieve data from the kernel buffers belonging to the given inode
1109+
//
1110+
// If successful then the kernel will send a NotifyRetrieveReplyOp as a reply.
1111+
// Only present pages are returned in the retrieve reply. Retrieving stops when it
1112+
// finds a non-present page and only data prior to that is returned.
1113+
//
1114+
// If this request returns an error, then the retrieve will not be completed and
1115+
// no reply will be sent.
1116+
//
1117+
// This request doesn't change the dirty state of pages in the kernel buffer. For
1118+
// dirty pages the write() method will be called regardless of having been retrieved
1119+
// previously.
1120+
type NotifyRetrieve struct {
1121+
Inode InodeID
1122+
Unique uint64
1123+
Offset uint64
1124+
Length uint32
1125+
}
1126+
1127+
// Matches the size of WriteIn
1128+
type NotifyRetrieveReplyOp struct {
1129+
Inode InodeID
1130+
Unique uint64
1131+
Offset uint64
1132+
Length uint32
1133+
1134+
OpContext OpContext
1135+
}

fuseutil/file_system.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ type FileSystem interface {
6666
SyncFS(context.Context, *fuseops.SyncFSOp) error
6767
Poll(context.Context, *fuseops.PollOp) error
6868

69+
SetConnection(*fuse.Connection)
70+
6971
// Regard all inodes (including the root inode) as having their lookup counts
7072
// decremented to zero, and clean up any resources associated with the file
7173
// system. No further calls to the file system will be made.
@@ -97,6 +99,8 @@ type fileSystemServer struct {
9799
}
98100

99101
func (s *fileSystemServer) ServeOps(c *fuse.Connection) {
102+
s.fs.SetConnection(c)
103+
100104
// When we are done, we clean up by waiting for all in-flight ops then
101105
// destroying the file system.
102106
defer func() {

fuseutil/not_implemented_file_system.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,5 +216,8 @@ func (fs *NotImplementedFileSystem) Poll(
216216
return fuse.ENOSYS
217217
}
218218

219+
func (fs *NotImplementedFileSystem) SetConnection(*fuse.Connection) {
220+
}
221+
219222
func (fs *NotImplementedFileSystem) Destroy() {
220223
}

0 commit comments

Comments
 (0)