From 43a4ca4680025a814b9e4dd4d39ed48aad637bc4 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 3 Aug 2026 00:55:50 -0700 Subject: [PATCH] mount: let readdir feed a sink instead of the kernel buffer doReadDirectory wrote directly into fuse.DirEntryList, which is the kernel's wire format. A front end that is not the kernel would have to pack entries only to parse them straight back out. Route it through DirEntrySink instead. ReadDir and ReadDirPlus pass the reply buffer, so nothing changes for the FUSE server. --- weed/mount/dir_sink.go | 37 +++++++++++++++++++++++++++++++++++ weed/mount/weedfs_dir_read.go | 20 +++++++++---------- 2 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 weed/mount/dir_sink.go diff --git a/weed/mount/dir_sink.go b/weed/mount/dir_sink.go new file mode 100644 index 000000000..81cb6cd3a --- /dev/null +++ b/weed/mount/dir_sink.go @@ -0,0 +1,37 @@ +package mount + +import ( + "github.com/seaweedfs/go-fuse/v2/fuse" +) + +// DirEntrySink receives the entries a readdir produces. The FUSE server packs +// them straight into the kernel's reply buffer; a front end that is not the +// kernel reads them out instead of re-parsing that wire format. +type DirEntrySink interface { + // AddEntry reports one entry, returning false once the sink is full. A + // full sink ends the batch; the client resumes from the entry's Off. + AddEntry(entry fuse.DirEntry) bool + + // AddEntryPlus is AddEntry for readdirplus, returning the attribute block + // to fill in, or nil once the sink is full. + AddEntryPlus(entry fuse.DirEntry) *fuse.EntryOut +} + +// fuseDirEntryList adapts the kernel reply buffer to DirEntrySink. +type fuseDirEntryList struct { + *fuse.DirEntryList +} + +func (l fuseDirEntryList) AddEntry(entry fuse.DirEntry) bool { + return l.AddDirEntry(entry) +} + +func (l fuseDirEntryList) AddEntryPlus(entry fuse.DirEntry) *fuse.EntryOut { + return l.AddDirLookupEntry(entry) +} + +// ReadDirectoryInto runs a readdir against sink. ReadDir and ReadDirPlus are +// this with the kernel reply buffer as the sink. +func (wfs *WFS) ReadDirectoryInto(input *fuse.ReadIn, sink DirEntrySink, isPlusMode bool) fuse.Status { + return wfs.doReadDirectory(input, sink, isPlusMode) +} diff --git a/weed/mount/weedfs_dir_read.go b/weed/mount/weedfs_dir_read.go index 488a69f38..dbdba0e97 100644 --- a/weed/mount/weedfs_dir_read.go +++ b/weed/mount/weedfs_dir_read.go @@ -139,14 +139,14 @@ func (wfs *WFS) FsyncDir(cancel <-chan struct{}, input *fuse.FsyncIn) (code fuse * '1'. */ func (wfs *WFS) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) { - return wfs.doReadDirectory(input, out, false) + return wfs.doReadDirectory(input, fuseDirEntryList{out}, false) } func (wfs *WFS) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) { - return wfs.doReadDirectory(input, out, true) + return wfs.doReadDirectory(input, fuseDirEntryList{out}, true) } -func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPlusMode bool) fuse.Status { +func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out DirEntrySink, isPlusMode bool) fuse.Status { // Get the directory handle and lock it for the duration of this operation. // This serializes concurrent readdir calls on the same handle, fixing the // race condition that caused hangs with NFS-Ganesha. @@ -182,11 +182,11 @@ func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPl dirEntry.Off = dh.entryStreamOffset + uint64(index) + 1 if !isPlusMode { - if !out.AddDirEntry(dirEntry) { + if !out.AddEntry(dirEntry) { return false } } else { - entryOut := out.AddDirLookupEntry(dirEntry) + entryOut := out.AddEntryPlus(dirEntry) if entryOut == nil { return false } @@ -203,14 +203,14 @@ func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPl if input.Offset < directoryStreamBaseOffset { if !isPlusMode { if input.Offset == 0 { - out.AddDirEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".", Off: 1}) + out.AddEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".", Off: 1}) } - out.AddDirEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "..", Off: 2}) + out.AddEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "..", Off: 2}) } else { if input.Offset == 0 { - out.AddDirLookupEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".", Off: 1}) + out.AddEntryPlus(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".", Off: 1}) } - out.AddDirLookupEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "..", Off: 2}) + out.AddEntryPlus(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "..", Off: 2}) } input.Offset = directoryStreamBaseOffset } @@ -296,7 +296,7 @@ func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPl return fuse.OK } -func (wfs *WFS) readDirectoryDirect(input *fuse.ReadIn, out *fuse.DirEntryList, dh *DirectoryHandle, dirPath util.FullPath, processEachEntryFn func(entry *filer.Entry, index int64) bool) fuse.Status { +func (wfs *WFS) readDirectoryDirect(input *fuse.ReadIn, out DirEntrySink, dh *DirectoryHandle, dirPath util.FullPath, processEachEntryFn func(entry *filer.Entry, index int64) bool) fuse.Status { var lastEntryName string if input.Offset >= dh.entryStreamOffset {