From 37d63df227ec3978215961d32a7d565abb9ae6a7 Mon Sep 17 00:00:00 2001 From: Doychin Atanasov Date: Fri, 27 May 2022 09:49:15 +0300 Subject: [PATCH] Add support for the FUSE_BATCH_FORGET operation (#123) There are certain Kernel versions which do not send individual Forget operations after they receive "not implemented" for Batch Forget. One example is "5.4.0-110-generic" on Ubuntu 20.04. I am sure there are plenty of others. This leads to inode "leaks". Where reference counts are never decreased and the inodes were left hanging around long after they are not needed any more. The best way to fix that was adding support for batch operations to the lib. This way all users will be able to benefit from the batching optimization. Co-authored-by: Doychin Atanasov --- conversions.go | 29 +++++++++++++++++++++++++ fuseops/ops.go | 26 ++++++++++++++++++++++ fuseutil/file_system.go | 4 ++++ fuseutil/not_implemented_file_system.go | 6 +++++ internal/fusekernel/fuse_kernel.go | 11 ++++++++++ 5 files changed, 76 insertions(+) diff --git a/conversions.go b/conversions.go index 4416412..c330f8a 100644 --- a/conversions.go +++ b/conversions.go @@ -113,6 +113,32 @@ func convertInMessage( OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid}, } + case fusekernel.OpBatchForget: + type input fusekernel.BatchForgetCountIn + in := (*input)(inMsg.Consume(unsafe.Sizeof(input{}))) + if in == nil { + return nil, errors.New("Corrupt OpBatchForget") + } + + entries := make([]fuseops.BatchForgetEntry, 0, in.Count) + for i := uint32(0); i < in.Count; i++ { + type entry fusekernel.BatchForgetEntryIn + ein := (*entry)(inMsg.Consume(unsafe.Sizeof(entry{}))) + if ein == nil { + return nil, errors.New("Corrupt OpBatchForget") + } + + entries = append(entries, fuseops.BatchForgetEntry{ + Inode: fuseops.InodeID(ein.Inode), + N: ein.Nlookup, + }) + } + + o = &fuseops.BatchForgetOp{ + Entries: entries, + OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid}, + } + case fusekernel.OpMkdir: in := (*fusekernel.MkdirIn)(inMsg.Consume(fusekernel.MkdirInSize(protocol))) if in == nil { @@ -595,6 +621,9 @@ func (c *Connection) kernelResponse( case *fuseops.ForgetInodeOp: return true + case *fuseops.BatchForgetOp: + return true + case *interruptOp: return true } diff --git a/fuseops/ops.go b/fuseops/ops.go index 9a0b8c5..06537d9 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -220,6 +220,32 @@ type ForgetInodeOp struct { OpContext OpContext } +// BatchForgetEntry represents one Inode entry to forget in the BatchForgetOp. +// +// Everything written in the ForgetInodeOp docs applies for the BatchForgetEntry +// too. +type BatchForgetEntry struct { + // The inode whose reference count should be decremented. + Inode InodeID + + // The amount to decrement the reference count. + N uint64 +} + +// Decrement the reference counts for a list of inode IDs previously issued by the file +// system. +// +// This operation is a batch of ForgetInodeOp operations. Every entry in +// Entries is one ForgetInodeOp operation. See the docs of ForgetInodeOp +// for further details. +type BatchForgetOp struct { + // Entries is a list of Forget operations. One could treat every entry in the + // list as a single ForgetInodeOp operation. + Entries []BatchForgetEntry + + OpContext OpContext +} + //////////////////////////////////////////////////////////////////////// // Inode creation //////////////////////////////////////////////////////////////////////// diff --git a/fuseutil/file_system.go b/fuseutil/file_system.go index 5eb8bca..b0e633a 100644 --- a/fuseutil/file_system.go +++ b/fuseutil/file_system.go @@ -39,6 +39,7 @@ type FileSystem interface { GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error ForgetInode(context.Context, *fuseops.ForgetInodeOp) error + BatchForget(context.Context, *fuseops.BatchForgetOp) error MkDir(context.Context, *fuseops.MkDirOp) error MkNode(context.Context, *fuseops.MkNodeOp) error CreateFile(context.Context, *fuseops.CreateFileOp) error @@ -151,6 +152,9 @@ func (s *fileSystemServer) handleOp( case *fuseops.ForgetInodeOp: err = s.fs.ForgetInode(ctx, typed) + case *fuseops.BatchForgetOp: + err = s.fs.BatchForget(ctx, typed) + case *fuseops.MkDirOp: err = s.fs.MkDir(ctx, typed) diff --git a/fuseutil/not_implemented_file_system.go b/fuseutil/not_implemented_file_system.go index 4d29cfa..cfb116c 100644 --- a/fuseutil/not_implemented_file_system.go +++ b/fuseutil/not_implemented_file_system.go @@ -60,6 +60,12 @@ func (fs *NotImplementedFileSystem) ForgetInode( return fuse.ENOSYS } +func (fs *NotImplementedFileSystem) BatchForget( + ctx context.Context, + op *fuseops.BatchForgetOp) error { + return fuse.ENOSYS +} + func (fs *NotImplementedFileSystem) MkDir( ctx context.Context, op *fuseops.MkDirOp) error { diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index e2d55ec..c1aded9 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -386,6 +386,7 @@ const ( OpDestroy = 38 OpIoctl = 39 // Linux? OpPoll = 40 // Linux? + OpBatchForget = 42 OpFallocate = 43 // OS X @@ -417,6 +418,16 @@ type ForgetIn struct { Nlookup uint64 } +type BatchForgetCountIn struct { + Count uint32 + dummy uint32 +} + +type BatchForgetEntryIn struct { + Inode int64 + Nlookup uint64 +} + type GetattrIn struct { GetattrFlags uint32 dummy uint32