Files
go-fuse/fs/dir_test.go

266 lines
5.6 KiB
Go

// Copyright 2023 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import (
"context"
"fmt"
"reflect"
"sync"
"syscall"
"testing"
"github.com/hanzoai/go-fuse/v2/fuse"
)
type dirStreamErrorNode struct {
Inode
}
var _ = (NodeReaddirer)((*dirStreamErrorNode)(nil))
func (n *dirStreamErrorNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
return &errDirStream{}, 0
}
type errDirStream struct {
num int
}
func (ds *errDirStream) HasNext() bool {
return ds.num < 2
}
func (ds *errDirStream) Next() (fuse.DirEntry, syscall.Errno) {
ds.num++
if ds.num == 1 {
return fuse.DirEntry{
Mode: fuse.S_IFREG,
Name: "first",
Ino: 2,
Off: 100,
}, 0
}
if ds.num == 2 {
return fuse.DirEntry{
Mode: fuse.S_IFREG,
Name: "last",
Ino: 3,
Off: 200,
}, syscall.EBADMSG
}
panic("boom")
}
func (ds *errDirStream) Close() {
}
func TestDirStreamError(t *testing.T) {
for _, disableReaddirplus := range []bool{false, true} {
t.Run(fmt.Sprintf("disableReaddirplus=%v", disableReaddirplus),
func(t *testing.T) {
root := &dirStreamErrorNode{}
opts := Options{}
opts.DisableReadDirPlus = disableReaddirplus
mnt, _ := testMount(t, root, &opts)
ds, errno := NewLoopbackDirStream(mnt)
if errno != 0 {
t.Fatalf("NewLoopbackDirStream: %v", errno)
}
defer ds.Close()
if !ds.HasNext() {
t.Fatal("expect HasNext")
}
if e, errno := ds.Next(); errno != 0 {
t.Errorf("ds.Next: %v", errno)
} else if e.Name != "first" {
t.Errorf("got %q want 'first'", e.Name)
} else if e.Off != 100 {
t.Errorf("got off %d, want 100", e.Off)
}
if !ds.HasNext() {
t.Fatalf("got !HasNext")
}
// Here we need choose a errno to test if errno could be passed and handled
// correctly by the fuse library. To build the test on different platform,
// an errno which defined on each platform should be chosen. And if the
// chosen integer number is not a valid errno, the fuse in kernel would refuse
// and throw an error, which is observed on Linux.
// Here we choose to use EBADMSG, which is defined on multiple Unix-like OSes.
if _, errno := ds.Next(); errno != syscall.EBADMSG {
t.Errorf("got errno %v, want EBADMSG", errno)
}
})
}
}
type dirStreamSeekNode struct {
Inode
num int
}
type listDirEntries struct {
entries []fuse.DirEntry
next int
}
var _ = (FileReaddirenter)((*listDirEntries)(nil))
func (l *listDirEntries) Readdirent(ctx context.Context) (*fuse.DirEntry, syscall.Errno) {
if l.next >= len(l.entries) {
return nil, 0
}
de := &l.entries[l.next]
l.next++
return de, 0
}
var _ = (FileSeekdirer)((*listDirEntries)(nil))
func (l *listDirEntries) Seekdir(ctx context.Context, off uint64) syscall.Errno {
if off == 0 {
l.next = 0
} else {
for i, e := range l.entries {
if e.Off == off {
l.next = i + 1
return 0
}
}
}
// TODO: error code if not found?
return 0
}
var _ = (NodeOpendirHandler)((*dirStreamSeekNode)(nil))
func (n *dirStreamSeekNode) OpendirHandle(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno) {
var l []fuse.DirEntry
for i := 0; i < n.num; i++ {
l = append(l, fuse.DirEntry{
Name: fmt.Sprintf("name%d", i),
Mode: fuse.S_IFREG,
Ino: uint64(i + 100),
Off: uint64((1 + (i*7)%n.num) * 100),
})
}
return &listDirEntries{entries: l}, 0, 0
}
func testDirSeek(t *testing.T, mnt string) {
ds, errno := NewLoopbackDirStream(mnt)
if errno != 0 {
t.Fatalf("NewLoopbackDirStream: %v", errno)
}
defer ds.Close()
fullResult, errno := readDirStream(ds)
if errno != 0 {
t.Fatalf("readDirStream: %v", errno)
}
for i, res := range fullResult {
func() {
ds, errno := NewLoopbackDirStream(mnt)
if errno != 0 {
t.Fatalf("NewLoopbackDirStream: %v", errno)
}
defer ds.Close()
if errno := ds.(*loopbackDirStream).Seekdir(context.Background(), res.Off); errno != 0 {
t.Fatalf("seek: %v", errno)
}
rest, errno := readDirStream(ds)
if errno != 0 {
t.Fatalf("readDirStream: %v", errno)
}
if rest == nil {
rest = fullResult[:0]
}
if want := fullResult[i+1:]; !reflect.DeepEqual(rest, want) {
t.Errorf("got %v, want %v", rest, want)
}
}()
}
}
func TestDirStreamSeek(t *testing.T) {
for _, rdp := range []bool{false, true} {
t.Run(fmt.Sprintf("readdirplus=%v", rdp),
func(t *testing.T) {
N := 11
root := &dirStreamSeekNode{num: N}
opts := Options{}
opts.DisableReadDirPlus = !rdp
mnt, _ := testMount(t, root, &opts)
testDirSeek(t, mnt)
})
}
}
type syncNode struct {
Inode
mu sync.Mutex
syncDirCount int
}
type syncDir struct {
node *syncNode
}
func (d *syncDir) Readdirent(ctx context.Context) (*fuse.DirEntry, syscall.Errno) {
return nil, 0
}
var _ = (FileFsyncdirer)((*syncDir)(nil))
func (d *syncDir) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno {
d.node.mu.Lock()
defer d.node.mu.Unlock()
d.node.syncDirCount++
return 0
}
var _ = (NodeOpendirHandler)((*syncNode)(nil))
func (n *syncNode) OpendirHandle(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno) {
return &syncDir{n}, 0, 0
}
func TestFsyncDir(t *testing.T) {
root := &syncNode{}
opts := Options{}
mnt, _ := testMount(t, root, &opts)
fd, err := syscall.Open(mnt, syscall.O_DIRECTORY, 0)
if err != nil {
t.Fatal(err)
}
if err := syscall.Fsync(fd); err != nil {
t.Fatal(err)
}
syscall.Close(fd)
root.mu.Lock()
defer root.mu.Unlock()
if root.syncDirCount != 1 {
t.Errorf("got %d, want 1", root.syncDirCount)
}
}