Commit 1002f0a1 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

nodefs: ReadDir/ReadDirPlus

parent 7e917a7b
...@@ -63,6 +63,41 @@ func InodeOf(node Operations) *Inode { ...@@ -63,6 +63,41 @@ func InodeOf(node Operations) *Inode {
return node.inode() return node.inode()
} }
// DirStream lists directory entries.
type DirStream interface {
// HasNext indicates if there are further entries. HasNext
// might be called on already closed streams.
HasNext() bool
// Next retrieves the next entry. It is only called if HasNext
// has previously returned true. The Status may be used to
// indicate I/O errors
Next() (fuse.DirEntry, fuse.Status)
// Close releases resources related to this directory
// stream. A stream should be resilient against double close.
Close()
}
// XXX names
type DirArray struct {
Entries []fuse.DirEntry
}
func (a *DirArray) HasNext() bool {
return len(a.Entries) > 0
}
func (a *DirArray) Next() (fuse.DirEntry, fuse.Status) {
e := a.Entries[0]
a.Entries = a.Entries[1:]
return e, fuse.OK
}
func (a *DirArray) Close() {
}
/* /*
NOSUBMIT: how to structure? NOSUBMIT: how to structure?
...@@ -110,6 +145,13 @@ type Operations interface { ...@@ -110,6 +145,13 @@ type Operations interface {
Readlink(ctx context.Context) (string, fuse.Status) Readlink(ctx context.Context) (string, fuse.Status)
Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, code fuse.Status) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, code fuse.Status)
// OpenDir is called for sanity/permission checks on opening a
// directory.
OpenDir(ctx context.Context) fuse.Status
// ReadDir opens a stream of directory entries.
ReadDir(ctx context.Context) (DirStream, fuse.Status)
Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, fuse.Status) Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, fuse.Status)
Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, code fuse.Status) Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, code fuse.Status)
......
...@@ -17,7 +17,10 @@ import ( ...@@ -17,7 +17,10 @@ import (
type fileEntry struct { type fileEntry struct {
file FileHandle file FileHandle
// space to hold directory stuff // Directory
dirStream DirStream
hasOverflow bool
overflow fuse.DirEntry
} }
type rawBridge struct { type rawBridge struct {
...@@ -31,7 +34,7 @@ type rawBridge struct { ...@@ -31,7 +34,7 @@ type rawBridge struct {
nodes map[uint64]*Inode nodes map[uint64]*Inode
automaticIno uint64 automaticIno uint64
files []fileEntry files []*fileEntry
freeFiles []uint64 freeFiles []uint64
} }
...@@ -110,7 +113,7 @@ func NewNodeFS(root Operations, opts *Options) fuse.RawFileSystem { ...@@ -110,7 +113,7 @@ func NewNodeFS(root Operations, opts *Options) fuse.RawFileSystem {
} }
// Fh 0 means no file handle. // Fh 0 means no file handle.
bridge.files = []fileEntry{{}} bridge.files = []*fileEntry{{}}
return bridge return bridge
} }
...@@ -118,16 +121,13 @@ func (b *rawBridge) String() string { ...@@ -118,16 +121,13 @@ func (b *rawBridge) String() string {
return "rawBridge" return "rawBridge"
} }
func (b *rawBridge) inode(id uint64, fh uint64) (*Inode, fileEntry) { func (b *rawBridge) inode(id uint64, fh uint64) (*Inode, *fileEntry) {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
n, f := b.nodes[id], b.files[fh] n, f := b.nodes[id], b.files[fh]
if n == nil { if n == nil {
log.Panicf("unknown node %d", id) log.Panicf("unknown node %d", id)
} }
if fh != 0 && f.file == nil {
log.Panicf("unknown fh %d", fh)
}
return n, f return n, f
} }
...@@ -457,7 +457,7 @@ func (b *rawBridge) registerFile(f FileHandle) uint64 { ...@@ -457,7 +457,7 @@ func (b *rawBridge) registerFile(f FileHandle) uint64 {
b.freeFiles = b.freeFiles[:last] b.freeFiles = b.freeFiles[:last]
} else { } else {
fh = uint64(len(b.files)) fh = uint64(len(b.files))
b.files = append(b.files, fileEntry{}) b.files = append(b.files, &fileEntry{})
} }
b.files[fh].file = f b.files[fh].file = f
...@@ -488,11 +488,19 @@ func (b *rawBridge) Release(input *fuse.ReleaseIn) { ...@@ -488,11 +488,19 @@ func (b *rawBridge) Release(input *fuse.ReleaseIn) {
n, f := b.inode(input.NodeId, input.Fh) n, f := b.inode(input.NodeId, input.Fh)
n.node.Release(context.TODO(), f.file) n.node.Release(context.TODO(), f.file)
if input.Fh > 0 { b.releaseFileEntry(input.Fh)
}
func (b *rawBridge) ReleaseDir(input *fuse.ReleaseIn) {
b.releaseFileEntry(input.Fh)
}
func (b *rawBridge) releaseFileEntry(fh uint64) {
if fh > 0 {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
b.files[input.Fh].file = nil b.files[fh].file = nil
b.freeFiles = append(b.freeFiles, input.Fh) b.freeFiles = append(b.freeFiles, fh)
} }
} }
...@@ -517,19 +525,120 @@ func (b *rawBridge) Fallocate(input *fuse.FallocateIn) (code fuse.Status) { ...@@ -517,19 +525,120 @@ func (b *rawBridge) Fallocate(input *fuse.FallocateIn) (code fuse.Status) {
} }
func (b *rawBridge) OpenDir(input *fuse.OpenIn, out *fuse.OpenOut) (status fuse.Status) { func (b *rawBridge) OpenDir(input *fuse.OpenIn, out *fuse.OpenOut) (status fuse.Status) {
return fuse.ENOSYS n, _ := b.inode(input.NodeId, 0)
code := n.node.OpenDir(context.TODO())
if !code.Ok() {
return code
}
b.mu.Lock()
defer b.mu.Unlock()
out.Fh = b.registerFile(nil)
return fuse.OK
}
func (b *rawBridge) getStream(input *fuse.ReadIn, inode *Inode, f *fileEntry) fuse.Status {
if f.dirStream == nil || input.Offset == 0 {
if f.dirStream != nil {
f.dirStream.Close()
}
str, code := inode.node.ReadDir(context.TODO())
if !code.Ok() {
return code
}
f.hasOverflow = false
f.dirStream = str
}
return fuse.OK
} }
func (b *rawBridge) ReadDir(input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { func (b *rawBridge) ReadDir(input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status {
return fuse.ENOSYS n, f := b.inode(input.NodeId, input.Fh)
if code := b.getStream(input, n, f); !code.Ok() {
return code
}
if f.hasOverflow {
// always succeeds.
out.AddDirEntry(f.overflow)
f.hasOverflow = false
}
// TODO - should post '..' and '.' ?
for f.dirStream.HasNext() {
e, code := f.dirStream.Next()
if !code.Ok() {
f.dirStream.Close()
return code
}
if !out.AddDirEntry(e) {
f.overflow = e
f.hasOverflow = true
return code
}
}
f.dirStream.Close()
return fuse.OK
} }
func (b *rawBridge) ReadDirPlus(input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { func (b *rawBridge) ReadDirPlus(input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status {
return fuse.ENOSYS n, f := b.inode(input.NodeId, input.Fh)
}
func (b *rawBridge) ReleaseDir(input *fuse.ReleaseIn) { if code := b.getStream(input, n, f); !code.Ok() {
return return code
}
if f.hasOverflow {
// always succeeds.
out.AddDirEntry(f.overflow)
f.hasOverflow = false
}
for f.dirStream.HasNext() {
var e fuse.DirEntry
var code fuse.Status
if f.hasOverflow {
e = f.overflow
f.hasOverflow = false
} else {
e, code = f.dirStream.Next()
}
if !code.Ok() {
f.dirStream.Close()
return code
}
entryOut := out.AddDirLookupEntry(e)
if entryOut == nil {
f.overflow = e
f.hasOverflow = true
return fuse.OK
}
child, code := n.node.Lookup(context.TODO(), e.Name, entryOut)
if !code.Ok() {
if b.options.NegativeTimeout != nil {
entryOut.SetEntryTimeout(*b.options.NegativeTimeout)
}
} else {
b.addNewChild(n, e.Name, child, nil, entryOut)
b.setEntryOutTimeout(entryOut)
if (e.Mode &^ 07777) != (child.mode &^ 07777) {
// XXX should go back and change the
// already serialized entry
log.Panicf("mode mismatch between readdir %o and lookup %o", e.Mode, child.mode)
}
entryOut.Mode = child.mode | (entryOut.Mode & 07777)
}
}
f.dirStream.Close()
return fuse.OK
} }
func (b *rawBridge) FsyncDir(input *fuse.FsyncIn) (code fuse.Status) { func (b *rawBridge) FsyncDir(input *fuse.FsyncIn) (code fuse.Status) {
......
...@@ -69,6 +69,14 @@ func (n *DefaultOperations) Unlink(ctx context.Context, name string) fuse.Status ...@@ -69,6 +69,14 @@ func (n *DefaultOperations) Unlink(ctx context.Context, name string) fuse.Status
return fuse.ENOSYS return fuse.ENOSYS
} }
func (n *DefaultOperations) OpenDir(ctx context.Context) fuse.Status {
return fuse.ENOSYS
}
func (n *DefaultOperations) ReadDir(ctx context.Context) (DirStream, fuse.Status) {
return nil, fuse.ENOSYS
}
func (n *DefaultOperations) Rename(ctx context.Context, name string, newParent Operations, newName string, flags uint32) fuse.Status { func (n *DefaultOperations) Rename(ctx context.Context, name string, newParent Operations, newName string, flags uint32) fuse.Status {
return fuse.ENOSYS return fuse.ENOSYS
} }
......
...@@ -6,6 +6,7 @@ package nodefs ...@@ -6,6 +6,7 @@ package nodefs
import ( import (
"context" "context"
"io"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
...@@ -255,6 +256,51 @@ func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, f ...@@ -255,6 +256,51 @@ func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, f
return lf, 0, fuse.OK return lf, 0, fuse.OK
} }
func (n *loopbackNode) OpenDir(ctx context.Context) fuse.Status {
fd, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0755)
if err != nil {
return fuse.ToStatus(err)
}
syscall.Close(fd)
return fuse.OK
}
func (n *loopbackNode) ReadDir(ctx context.Context) (DirStream, fuse.Status) {
// XXX should implement streaming read to make sure the API works.
f, err := os.Open(n.path())
if err != nil {
return nil, fuse.ToStatus(err)
}
defer f.Close()
var entries []fuse.DirEntry
for {
want := 100
infos, err := f.Readdir(want)
for _, info := range infos {
s := fuse.ToStatT(info)
if s == nil {
continue
}
entries = append(entries, fuse.DirEntry{
Name: info.Name(),
Mode: uint32(s.Mode),
Ino: s.Ino,
})
}
if len(infos) < want || err == io.EOF {
break
}
if err != nil {
return nil, fuse.ToStatus(err)
}
}
return &DirArray{entries}, fuse.OK
}
func (n *loopbackNode) fGetAttr(ctx context.Context, out *fuse.AttrOut) (fuse.Status, bool) { func (n *loopbackNode) fGetAttr(ctx context.Context, out *fuse.AttrOut) (fuse.Status, bool) {
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
......
...@@ -6,6 +6,7 @@ package nodefs ...@@ -6,6 +6,7 @@ package nodefs
import ( import (
"bytes" "bytes"
"fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
...@@ -492,6 +493,39 @@ func TestNotifyEntry(t *testing.T) { ...@@ -492,6 +493,39 @@ func TestNotifyEntry(t *testing.T) {
} }
} }
// Test Notify() , but requires KEEP_CACHE. // XXX Test Notify() , but requires KEEP_CACHE ? or could use mmap?
// XXX Test NotifyDelete?
// Test NotifyDelete? func TestReadDir(t *testing.T) {
tc := newTestCase(t)
defer tc.Clean()
// XXX what about ".." and "." ?
want := map[string]bool{}
for i := 0; i < 2; i++ {
// 40 bytes of filename, so 110 entries overflows a
// 4096 page.
nm := fmt.Sprintf("file%036x", i)
want[nm] = true
if err := ioutil.WriteFile(tc.origDir+"/"+nm, []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
}
entries, err := ioutil.ReadDir(tc.mntDir)
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
got := map[string]bool{}
for _, e := range entries {
got[e.Name()] = true
}
if len(got) != len(want) {
t.Errorf("got %d entries, want %d", len(got), len(want))
}
for k := range got {
if !want[k] {
t.Errorf("got unknown name %q", k)
}
}
}
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