From efe41d860d78a2b2e564270c4bc1168b8cbb9ab8 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Wed, 2 Aug 2023 00:09:40 +0300 Subject: [PATCH] Add READDIRPLUS support --- connection.go | 6 ++++++ conversions.go | 3 +++ fuseops/ops.go | 8 +++++++- fuseutil/dirent.go | 24 ++++++++++++++++++++++-- internal/fusekernel/fuse_kernel.go | 1 + mount_config.go | 5 +++++ 6 files changed, 44 insertions(+), 3 deletions(-) diff --git a/connection.go b/connection.go index 7bebe0b..74b2df5 100644 --- a/connection.go +++ b/connection.go @@ -151,6 +151,7 @@ func (c *Connection) Init() error { cacheSymlinks := initOp.Flags&fusekernel.InitCacheSymlinks > 0 noOpenSupport := initOp.Flags&fusekernel.InitNoOpenSupport > 0 noOpendirSupport := initOp.Flags&fusekernel.InitNoOpendirSupport > 0 + readdirplusSupport := initOp.Flags&fusekernel.InitDoReaddirplus > 0 // Respond to the init op. initOp.Library = c.protocol @@ -193,6 +194,11 @@ func (c *Connection) Init() error { initOp.Flags |= fusekernel.InitNoOpendirSupport } + // Tell the kernel to do readdirplus (readdir+lookup in one call) + if c.cfg.UseReadDirPlus && readdirplusSupport { + initOp.Flags |= fusekernel.InitDoReaddirplus + } + c.Reply(ctx, nil) return nil } diff --git a/conversions.go b/conversions.go index c64ee74..8529107 100644 --- a/conversions.go +++ b/conversions.go @@ -341,6 +341,8 @@ func convertInMessage( } o = to + case fusekernel.OpReaddirplus: + fallthrough case fusekernel.OpReaddir: in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol))) if in == nil { @@ -351,6 +353,7 @@ func convertInMessage( Inode: fuseops.InodeID(inMsg.Header().Nodeid), Handle: fuseops.HandleID(in.Fh), Offset: fuseops.DirOffset(in.Offset), + Plus: inMsg.Header().Opcode == fusekernel.OpReaddirplus, OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid}, } o = to diff --git a/fuseops/ops.go b/fuseops/ops.go index 3f16051..2e931da 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -562,12 +562,18 @@ type ReadDirOp struct { // offset, and return array offsets into that cached listing. 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 output data should consist of a sequence of FUSE directory entries in // the format generated by fuse_add_direntry (http://goo.gl/qCcHCV), which is // consumed by parse_dirfile (http://goo.gl/2WUmD2). Use fuseutil.WriteDirent - // to generate this data. + // or fuseutil.WriteDirentPlus to generate this data. // // 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 diff --git a/fuseutil/dirent.go b/fuseutil/dirent.go index 2fd3bea..d3212b3 100644 --- a/fuseutil/dirent.go +++ b/fuseutil/dirent.go @@ -19,6 +19,7 @@ import ( "unsafe" "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/fuse/internal/fusekernel" ) type DirentType uint32 @@ -50,10 +51,18 @@ type Dirent struct { Type DirentType } -// Write the supplied directory entry intto the given buffer in the format -// expected in fuseops.ReadFileOp.Data, returning the number of bytes written. +// Write the supplied directory entry into the given buffer in the format +// expected in fuseops.ReadDirOp.Data, returning the number of bytes written. // Return zero if the entry would not fit. 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 // (http://goo.gl/BmFxob) in host order. The struct must be aligned according // to FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH), which dictates 8-byte @@ -78,10 +87,21 @@ func WriteDirent(buf []byte, d Dirent) (n int) { // Do we have enough room? 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) { 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. de := fuse_dirent{ ino: uint64(d.Inode), diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index c00b000..346dff7 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -417,6 +417,7 @@ const ( OpNotifyReply = 41 OpBatchForget = 42 OpFallocate = 43 + OpReaddirplus = 44 // OS X OpSetvolname = 61 diff --git a/mount_config.go b/mount_config.go index 86be126..a4b09b6 100644 --- a/mount_config.go +++ b/mount_config.go @@ -151,6 +151,11 @@ type MountConfig struct { // OpenDir calls at all (Linux >= 5.1): 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. // This is useful for situations where the backing data store (e.g., S3) doesn't // actually utilise any form of qualifiable UNIX permissions.