Commit 8783b8e8 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

For readdirplus, execute lookups when adding name entry.

We can only do the lookup if we are sure that we will return the
result to the kernel, otherwise our lookup counts will go out of sync
with the kernel. As a bonus, this removes the allocation for the
EntryOut data structure, since we can write directly into the
readdirplus return data.

Add a port of the xfstest generic/257 that triggered this bug.

Fixes #57.
parent c4179aac
...@@ -47,14 +47,14 @@ func NewDirEntryList(data []byte, off uint64) *DirEntryList { ...@@ -47,14 +47,14 @@ func NewDirEntryList(data []byte, off uint64) *DirEntryList {
// AddDirEntry tries to add an entry, and reports whether it // AddDirEntry tries to add an entry, and reports whether it
// succeeded. // succeeded.
func (l *DirEntryList) AddDirEntry(e DirEntry) (bool, uint64) { func (l *DirEntryList) AddDirEntry(e DirEntry) (bool, uint64) {
return l.Add(nil, e.Name, uint64(FUSE_UNKNOWN_INO), e.Mode) return l.Add(0, e.Name, uint64(FUSE_UNKNOWN_INO), e.Mode)
} }
// Add adds a direntry to the DirEntryList, returning whether it // Add adds a direntry to the DirEntryList, returning whether it
// succeeded. // succeeded.
func (l *DirEntryList) Add(prefix []byte, name string, inode uint64, mode uint32) (bool, uint64) { func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) (bool, uint64) {
padding := (8 - len(name)&7) & 7 padding := (8 - len(name)&7) & 7
delta := padding + direntSize + len(name) + len(prefix) delta := padding + direntSize + len(name) + prefix
oldLen := len(l.buf) oldLen := len(l.buf)
newLen := delta + oldLen newLen := delta + oldLen
...@@ -62,8 +62,7 @@ func (l *DirEntryList) Add(prefix []byte, name string, inode uint64, mode uint32 ...@@ -62,8 +62,7 @@ func (l *DirEntryList) Add(prefix []byte, name string, inode uint64, mode uint32
return false, l.offset return false, l.offset
} }
l.buf = l.buf[:newLen] l.buf = l.buf[:newLen]
copy(l.buf[oldLen:], prefix) oldLen += prefix
oldLen += len(prefix)
dirent := (*_Dirent)(unsafe.Pointer(&l.buf[oldLen])) dirent := (*_Dirent)(unsafe.Pointer(&l.buf[oldLen]))
dirent.Off = l.offset + 1 dirent.Off = l.offset + 1
dirent.Ino = inode dirent.Ino = inode
...@@ -82,17 +81,16 @@ func (l *DirEntryList) Add(prefix []byte, name string, inode uint64, mode uint32 ...@@ -82,17 +81,16 @@ func (l *DirEntryList) Add(prefix []byte, name string, inode uint64, mode uint32
} }
// AddDirLookupEntry is used for ReadDirPlus. It serializes a DirEntry // AddDirLookupEntry is used for ReadDirPlus. It serializes a DirEntry
// and its corresponding lookup. Pass a zero entryOut if the lookup // and returns the space for entry. If no space is left, returns a nil
// data should be ignored. // pointer.
func (l *DirEntryList) AddDirLookupEntry(e DirEntry, entryOut *EntryOut) (bool, uint64) { func (l *DirEntryList) AddDirLookupEntry(e DirEntry) (*EntryOut, uint64) {
ino := uint64(FUSE_UNKNOWN_INO) lastStart := len(l.buf)
if entryOut.Ino > 0 { ok, off := l.Add(int(unsafe.Sizeof(EntryOut{})), e.Name,
ino = entryOut.Ino uint64(FUSE_UNKNOWN_INO), e.Mode)
if !ok {
return nil, off
} }
var lookup []byte return (*EntryOut)(unsafe.Pointer(&l.buf[lastStart])), off
toSlice(&lookup, unsafe.Pointer(entryOut), unsafe.Sizeof(EntryOut{}))
return l.Add(lookup, e.Name, ino, e.Mode)
} }
func (l *DirEntryList) bytes() []byte { func (l *DirEntryList) bytes() []byte {
......
...@@ -11,7 +11,6 @@ type connectorDir struct { ...@@ -11,7 +11,6 @@ type connectorDir struct {
stream []fuse.DirEntry stream []fuse.DirEntry
lastOffset uint64 lastOffset uint64
rawFS fuse.RawFileSystem rawFS fuse.RawFileSystem
lookups []fuse.EntryOut
} }
func (d *connectorDir) ReadDir(input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) { func (d *connectorDir) ReadDir(input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) {
...@@ -58,21 +57,6 @@ func (d *connectorDir) ReadDirPlus(input *fuse.ReadIn, out *fuse.DirEntryList) ( ...@@ -58,21 +57,6 @@ func (d *connectorDir) ReadDirPlus(input *fuse.ReadIn, out *fuse.DirEntryList) (
if !code.Ok() { if !code.Ok() {
return code return code
} }
d.lookups = nil
}
if d.lookups == nil {
d.lookups = make([]fuse.EntryOut, len(d.stream))
for i, n := range d.stream {
if n.Name == "." || n.Name == ".." {
continue
}
// We ignore the return value
code := d.rawFS.Lookup(&input.InHeader, n.Name, &d.lookups[i])
if !code.Ok() {
d.lookups[i] = fuse.EntryOut{}
}
}
} }
if input.Offset > uint64(len(d.stream)) { if input.Offset > uint64(len(d.stream)) {
...@@ -80,16 +64,31 @@ func (d *connectorDir) ReadDirPlus(input *fuse.ReadIn, out *fuse.DirEntryList) ( ...@@ -80,16 +64,31 @@ func (d *connectorDir) ReadDirPlus(input *fuse.ReadIn, out *fuse.DirEntryList) (
return fuse.EINVAL return fuse.EINVAL
} }
todo := d.stream[input.Offset:] todo := d.stream[input.Offset:]
for i, e := range todo { for _, e := range todo {
if e.Name == "" { if e.Name == "" {
log.Printf("got empty directory entry, mode %o.", e.Mode) log.Printf("got empty directory entry, mode %o.", e.Mode)
continue continue
} }
ok, off := out.AddDirLookupEntry(e, &d.lookups[input.Offset+uint64(i)])
d.lastOffset = off if e.Name == "." || e.Name == ".." {
if !ok { continue
}
// we have to be sure entry will fit if we try to add
// it, or we'll mess up the lookup counts.
entryDest, off := out.AddDirLookupEntry(e)
if entryDest == nil {
break break
} }
entryDest.Ino = uint64(fuse.FUSE_UNKNOWN_INO)
// We ignore the return value
code := d.rawFS.Lookup(&input.InHeader, e.Name, entryDest)
if !code.Ok() {
// if something went wrong, clear out the entry.
*entryDest = fuse.EntryOut{}
}
d.lastOffset = off
} }
return fuse.OK return fuse.OK
......
package test
import (
"fmt"
"os"
"path/filepath"
"syscall"
"testing"
"unsafe"
)
func clen(n []byte) int {
for i := 0; i < len(n); i++ {
if n[i] == 0 {
return i
}
}
return len(n)
}
type CDirent struct {
ino uint64
ntype uint8
off int64
name string
}
func parseDirents(buf []byte) []CDirent {
var result []CDirent
for len(buf) > 0 {
de := *(*syscall.Dirent)(unsafe.Pointer(&buf[0]))
buf = buf[de.Reclen:]
bytes := (*[10000]byte)(unsafe.Pointer(&de.Name[0]))
var name = string(bytes[0:clen(bytes[:])])
result = append(result, CDirent{
ino: de.Ino,
ntype: de.Type,
off: de.Off,
name: name,
})
}
return result
}
// This test is inspired on xfstest, t_dir_offset2.c
func TestReaddirPlusSeek(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
var names []string
for i := 0; i < 20; i++ {
names = append(names, fmt.Sprintf("abcd%d", i))
}
for _, n := range names {
if err := os.MkdirAll(filepath.Join(tc.origSubdir, n), 0755); err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
}
fd, err := syscall.Open(tc.mountSubdir, syscall.O_RDONLY, 0755)
if err != nil {
t.Fatalf("Open(%q): %v", tc.mountSubdir, err)
}
defer syscall.Close(int(fd))
// store offsets
type entryOff struct {
ino uint64
off int64
}
previous := map[int]entryOff{}
var bufdata [1024]byte
for {
buf := bufdata[:]
n, err := syscall.ReadDirent(fd, buf)
if err != nil {
t.Fatalf("ReadDirent: %v", err)
}
if n == 0 {
break
}
buf = buf[:n]
for _, d := range parseDirents(buf) {
if d.name == "." || d.name == ".." {
continue
}
i := len(previous)
previous[i] = entryOff{d.ino, d.off}
}
}
for i := len(previous) - 1; i >= 0; i-- {
var off int64
if i > 0 {
off = previous[i-1].off
}
if _, err := syscall.Seek(fd, off, 0); err != nil {
t.Fatalf("Seek %v", err)
}
buf := bufdata[:]
n, err := syscall.ReadDirent(fd, buf)
if err != nil {
t.Fatalf("readdir after seek %d: %v", i, err)
}
if n == 0 {
t.Fatalf("no dirent after seek to %d", i)
}
ds := parseDirents(buf[:n])
if ds[0].ino != previous[i].ino {
t.Errorf("got ino %d, want %d", ds[0].ino, previous[i].ino)
}
}
// Delete has a forget as side-effect: make sure we get the lookup counts correct.
for _, n := range names {
full := filepath.Join(tc.mountSubdir, n)
if err := syscall.Rmdir(full); err != nil {
t.Fatalf("Rmdir(%q): %v", n, err)
}
}
}
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