Commit 111c2926 authored by Jakob Unterwurzacher's avatar Jakob Unterwurzacher

fs: bridge: implement simple directory seeking

"seeking" works by skipping forward to the requested offset,
first rewinding to zero if seeking back.

The performance is obviously bad, however, seeking
inside a directory does not seem to be too common.

I think the solution is good enough until we see a
use case where this causes performance issues.

Fixes https://github.com/hanwen/go-fuse/issues/344

Change-Id: Ia7f7fbffaf932c16919eafede6b70f4aff245c25
parent 73071346
...@@ -33,6 +33,12 @@ type fileEntry struct { ...@@ -33,6 +33,12 @@ type fileEntry struct {
dirStream DirStream dirStream DirStream
hasOverflow bool hasOverflow bool
overflow fuse.DirEntry overflow fuse.DirEntry
// dirOffset is the current location in the directory (see `telldir(3)`).
// The value is equivalent to `d_off` (see `getdents(2)`) of the last
// directory entry sent to the kernel so far.
// If `dirOffset` and `fuse.DirEntryList.offset` disagree, then a
// directory seek has taken place.
dirOffset uint64
wg sync.WaitGroup wg sync.WaitGroup
} }
...@@ -842,23 +848,47 @@ func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fus ...@@ -842,23 +848,47 @@ func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fus
return fuse.OK return fuse.OK
} }
// setStream sets the directory part of f. Must hold f.mu // setStream makes sure `f.dirStream` and associated state variables are set and
func (b *rawBridge) setStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) syscall.Errno { // seeks to offset requested in `input`. Caller must hold `f.mu`.
if f.dirStream == nil || input.Offset == 0 { // The `eof` return value shows if `f.dirStream` ended before the requested
// offset was reached.
func (b *rawBridge) setStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) (errno syscall.Errno, eof bool) {
// Get a new directory stream in the following cases:
// 1) f.dirStream == nil ............ First READDIR[PLUS] on this file handle.
// 2) input.Offset == 0 ............. Start reading the directory again from
// the beginning (user called rewinddir(3) or lseek(2)).
// 3) input.Offset < f.nextOffset ... Seek back (user called seekdir(3) or lseek(2)).
if f.dirStream == nil || input.Offset == 0 || input.Offset < f.dirOffset {
if f.dirStream != nil { if f.dirStream != nil {
f.dirStream.Close() f.dirStream.Close()
f.dirStream = nil f.dirStream = nil
} }
str, errno := b.getStream(&fuse.Context{Caller: input.Caller, Cancel: cancel}, inode) str, errno := b.getStream(&fuse.Context{Caller: input.Caller, Cancel: cancel}, inode)
if errno != 0 { if errno != 0 {
return errno return errno, false
} }
f.dirOffset = 0
f.hasOverflow = false f.hasOverflow = false
f.dirStream = str f.dirStream = str
} }
return 0 // Seek forward?
for f.dirOffset < input.Offset {
f.hasOverflow = false
if !f.dirStream.HasNext() {
// Seek past end of directory. This is not an error, but the
// user will get an empty directory listing.
return 0, true
}
_, errno := f.dirStream.Next()
if errno != 0 {
return errno, true
}
f.dirOffset++
}
return 0, false
} }
func (b *rawBridge) getStream(ctx context.Context, inode *Inode) (DirStream, syscall.Errno) { func (b *rawBridge) getStream(ctx context.Context, inode *Inode) (DirStream, syscall.Errno) {
...@@ -880,14 +910,19 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus ...@@ -880,14 +910,19 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus
f.mu.Lock() f.mu.Lock()
defer f.mu.Unlock() defer f.mu.Unlock()
if errno := b.setStream(cancel, input, n, f); errno != 0 {
errno, eof := b.setStream(cancel, input, n, f)
if errno != 0 {
return errnoToStatus(errno) return errnoToStatus(errno)
} else if eof {
return fuse.OK
} }
if f.hasOverflow { if f.hasOverflow {
// always succeeds. // always succeeds.
out.AddDirEntry(f.overflow) out.AddDirEntry(f.overflow)
f.hasOverflow = false f.hasOverflow = false
f.dirOffset++
} }
for f.dirStream.HasNext() { for f.dirStream.HasNext() {
...@@ -901,6 +936,7 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus ...@@ -901,6 +936,7 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus
f.hasOverflow = true f.hasOverflow = true
return errnoToStatus(errno) return errnoToStatus(errno)
} }
f.dirOffset++
} }
return fuse.OK return fuse.OK
...@@ -911,8 +947,12 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out ...@@ -911,8 +947,12 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
f.mu.Lock() f.mu.Lock()
defer f.mu.Unlock() defer f.mu.Unlock()
if errno := b.setStream(cancel, input, n, f); errno != 0 {
errno, eof := b.setStream(cancel, input, n, f)
if errno != 0 {
return errnoToStatus(errno) return errnoToStatus(errno)
} else if eof {
return fuse.OK
} }
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
...@@ -937,6 +977,7 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out ...@@ -937,6 +977,7 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
f.hasOverflow = true f.hasOverflow = true
return fuse.OK return fuse.OK
} }
f.dirOffset++
// Virtual entries "." and ".." should be part of the // Virtual entries "." and ".." should be part of the
// directory listing, but not part of the filesystem tree. // directory listing, but not part of the filesystem tree.
......
...@@ -36,10 +36,17 @@ func (d DirEntry) String() string { ...@@ -36,10 +36,17 @@ func (d DirEntry) String() string {
// DirEntryList holds the return value for READDIR and READDIRPLUS // DirEntryList holds the return value for READDIR and READDIRPLUS
// opcodes. // opcodes.
type DirEntryList struct { type DirEntryList struct {
buf []byte buf []byte
size int // capacity of the underlying buffer // capacity of the underlying buffer
offset uint64 // entry count (NOT a byte offset) size int
lastDirent *_Dirent // pointer to the last serialized _Dirent. Used by FixMode(). // offset is the requested location in the directory. go-fuse
// currently counts in number of directory entries, but this is an
// implementation detail and may change in the future.
// If `offset` and `fs.fileEntry.dirOffset` disagree, then a
// directory seek has taken place.
offset uint64
// pointer to the last serialized _Dirent. Used by FixMode().
lastDirent *_Dirent
} }
// NewDirEntryList creates a DirEntryList with the given data buffer // NewDirEntryList creates a DirEntryList with the given data buffer
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment