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 {
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?
......@@ -110,6 +145,13 @@ type Operations interface {
Readlink(ctx context.Context) (string, 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)
Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, code fuse.Status)
......
......@@ -17,7 +17,10 @@ import (
type fileEntry struct {
file FileHandle
// space to hold directory stuff
// Directory
dirStream DirStream
hasOverflow bool
overflow fuse.DirEntry
}
type rawBridge struct {
......@@ -31,7 +34,7 @@ type rawBridge struct {
nodes map[uint64]*Inode
automaticIno uint64
files []fileEntry
files []*fileEntry
freeFiles []uint64
}
......@@ -110,7 +113,7 @@ func NewNodeFS(root Operations, opts *Options) fuse.RawFileSystem {
}
// Fh 0 means no file handle.
bridge.files = []fileEntry{{}}
bridge.files = []*fileEntry{{}}
return bridge
}
......@@ -118,16 +121,13 @@ func (b *rawBridge) String() string {
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()
defer b.mu.Unlock()
n, f := b.nodes[id], b.files[fh]
if n == nil {
log.Panicf("unknown node %d", id)
}
if fh != 0 && f.file == nil {
log.Panicf("unknown fh %d", fh)
}
return n, f
}
......@@ -457,7 +457,7 @@ func (b *rawBridge) registerFile(f FileHandle) uint64 {
b.freeFiles = b.freeFiles[:last]
} else {
fh = uint64(len(b.files))
b.files = append(b.files, fileEntry{})
b.files = append(b.files, &fileEntry{})
}
b.files[fh].file = f
......@@ -488,11 +488,19 @@ func (b *rawBridge) Release(input *fuse.ReleaseIn) {
n, f := b.inode(input.NodeId, input.Fh)
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()
defer b.mu.Unlock()
b.files[input.Fh].file = nil
b.freeFiles = append(b.freeFiles, input.Fh)
b.files[fh].file = nil
b.freeFiles = append(b.freeFiles, fh)
}
}
......@@ -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) {
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 {
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 {
return fuse.ENOSYS
}
n, f := b.inode(input.NodeId, input.Fh)
func (b *rawBridge) ReleaseDir(input *fuse.ReleaseIn) {
return
if code := b.getStream(input, n, f); !code.Ok() {
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) {
......
......@@ -69,6 +69,14 @@ func (n *DefaultOperations) Unlink(ctx context.Context, name string) fuse.Status
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 {
return fuse.ENOSYS
}
......
......@@ -6,6 +6,7 @@ package nodefs
import (
"context"
"io"
"log"
"os"
"path/filepath"
......@@ -255,6 +256,51 @@ func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, f
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) {
n.mu.Lock()
defer n.mu.Unlock()
......
......@@ -6,6 +6,7 @@ package nodefs
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
......@@ -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