Files
go-fuse/fs/cache_test.go

331 lines
7.1 KiB
Go

// Copyright 2019 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 (
"bytes"
"context"
"fmt"
"os"
"sync"
"syscall"
"testing"
"time"
"github.com/hanzoai/go-fuse/v2/fuse"
"github.com/hanzoai/go-fuse/v2/internal/testutil"
)
type keepCacheFile struct {
Inode
keepCache bool
mu sync.Mutex
content []byte
count int
}
var _ = (NodeReader)((*keepCacheFile)(nil))
var _ = (NodeOpener)((*keepCacheFile)(nil))
var _ = (NodeGetattrer)((*keepCacheFile)(nil))
func (f *keepCacheFile) setContent(delta int) {
f.mu.Lock()
defer f.mu.Unlock()
f.count += delta
f.content = []byte(fmt.Sprintf("%010x", f.count))
}
func (f *keepCacheFile) Open(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno) {
var fl uint32
if f.keepCache {
fl = fuse.FOPEN_KEEP_CACHE
}
f.setContent(0)
return nil, fl, OK
}
func (f *keepCacheFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
out.Size = uint64(len(f.content))
return OK
}
func (f *keepCacheFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
f.setContent(1)
f.mu.Lock()
defer f.mu.Unlock()
return fuse.ReadResultData(f.content[off:]), OK
}
type keepCacheRoot struct {
Inode
keep, nokeep *keepCacheFile
}
var _ = (NodeOnAdder)((*keepCacheRoot)(nil))
func (r *keepCacheRoot) OnAdd(ctx context.Context) {
i := &r.Inode
r.keep = &keepCacheFile{
keepCache: true,
}
r.keep.setContent(0)
i.AddChild("keep", i.NewInode(ctx, r.keep, StableAttr{}), true)
r.nokeep = &keepCacheFile{
keepCache: false,
}
r.nokeep.setContent(0)
i.AddChild("nokeep", i.NewInode(ctx, r.nokeep, StableAttr{}), true)
}
// Test FOPEN_KEEP_CACHE. This is a little subtle: the automatic cache
// invalidation triggers if mtime or file size is changed, so only
// change content but no metadata.
func TestKeepCache(t *testing.T) {
root := &keepCacheRoot{}
mntDir, _ := testMount(t, root, nil)
c1, err := os.ReadFile(mntDir + "/keep")
if err != nil {
t.Fatalf("read keep 1: %v", err)
}
c2, err := os.ReadFile(mntDir + "/keep")
if err != nil {
t.Fatalf("read keep 2: %v", err)
}
if bytes.Compare(c1, c2) != 0 {
t.Errorf("keep read 2 got %q want read 1 %q", c2, c1)
}
if s := root.keep.Inode.NotifyContent(0, 100); s != OK {
t.Errorf("NotifyContent: %v", s)
}
c3, err := os.ReadFile(mntDir + "/keep")
if err != nil {
t.Fatalf("read keep 3: %v", err)
}
if bytes.Compare(c2, c3) == 0 {
t.Errorf("keep read 3 got %q want different", c3)
}
nc1, err := os.ReadFile(mntDir + "/nokeep")
if err != nil {
t.Fatalf("read keep 1: %v", err)
}
nc2, err := os.ReadFile(mntDir + "/nokeep")
if err != nil {
t.Fatalf("read keep 2: %v", err)
}
if bytes.Compare(nc1, nc2) == 0 {
t.Errorf("nokeep read 2 got %q want read 1 %q", c2, c1)
}
}
type countingSymlink struct {
Inode
mu sync.Mutex
readCount int
data []byte
}
var _ = (NodeGetattrer)((*countingSymlink)(nil))
func (l *countingSymlink) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
l.mu.Lock()
defer l.mu.Unlock()
out.Attr.Size = uint64(len(l.data))
return 0
}
var _ = (NodeReadlinker)((*countingSymlink)(nil))
func (l *countingSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
l.mu.Lock()
defer l.mu.Unlock()
l.readCount++
return l.data, 0
}
func (l *countingSymlink) count() int {
l.mu.Lock()
defer l.mu.Unlock()
return l.readCount
}
func TestSymlinkCaching(t *testing.T) {
mnt := t.TempDir()
want := "target"
link := countingSymlink{
data: []byte(want),
}
sz := len(link.data)
root := &Inode{}
dt := 10 * time.Millisecond
opts := &Options{
EntryTimeout: &dt,
AttrTimeout: &dt,
OnAdd: func(ctx context.Context) {
root.AddChild("link",
root.NewPersistentInode(ctx, &link, StableAttr{Mode: syscall.S_IFLNK}), false)
},
}
opts.Debug = testutil.VerboseTest()
opts.EnableSymlinkCaching = true
server, err := Mount(mnt, root, opts)
if err != nil {
t.Fatal(err)
}
defer server.Unmount()
for i := 0; i < 2; i++ {
if got, err := os.Readlink(mnt + "/link"); err != nil {
t.Fatal(err)
} else if got != want {
t.Fatalf("got %q want %q", got, want)
}
}
if c := link.count(); c != 1 {
t.Errorf("got %d want 1", c)
}
if errno := link.NotifyContent(0, int64(sz)); errno != 0 {
t.Fatalf("NotifyContent: %v", errno)
}
if _, err := os.Readlink(mnt + "/link"); err != nil {
t.Fatal(err)
}
if c := link.count(); c != 2 {
t.Errorf("got %d want 2", c)
}
// The actual test goes till here. The below is just to
// clarify behavior of the feature: changed attributes do not
// trigger reread, and the Attr.Size is used to truncate a
// previous read result.
link.mu.Lock()
link.data = []byte("x")
link.mu.Unlock()
time.Sleep((3 * dt) / 2)
if l, err := os.Readlink(mnt + "/link"); err != nil {
t.Fatal(err)
} else if l != want[:1] {
t.Logf("got %q want %q", l, want[:1])
}
if c := link.count(); c != 2 {
t.Errorf("got %d want 2", c)
}
}
type autoInvalNode struct {
Inode
mu sync.Mutex
content []byte
mtime time.Time
}
var _ = (NodeOpener)((*autoInvalNode)(nil))
func (f *autoInvalNode) Open(ctx context.Context, openFlags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
return nil, 0, 0
}
var _ = (NodeReader)((*autoInvalNode)(nil))
func (f *autoInvalNode) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
return fuse.ReadResultData(f.content[off : int(off)+len(dest)]), OK
}
var _ = (NodeGetattrer)((*autoInvalNode)(nil))
func (f *autoInvalNode) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
out.Size = uint64(len(f.content))
out.SetTimes(nil, &f.mtime, nil)
return OK
}
/* Test AUTO_INVAL_DATA */
func TestAutoInvalData(t *testing.T) {
mnt := t.TempDir()
node := autoInvalNode{
content: bytes.Repeat([]byte{'x'}, 4096),
mtime: time.Now(),
}
root := &Inode{}
dt := 10 * time.Millisecond
opts := &Options{
EntryTimeout: &dt,
AttrTimeout: &dt,
OnAdd: func(ctx context.Context) {
root.AddChild("file",
root.NewPersistentInode(ctx, &node, StableAttr{Mode: syscall.S_IFREG}), false)
},
}
opts.Debug = testutil.VerboseTest()
srv, err := Mount(mnt, root, opts)
if err != nil {
t.Fatal(err)
}
defer srv.Unmount()
f, err := os.Open(mnt + "/file")
if err != nil {
t.Fatal(err)
}
defer f.Close()
data := make([]byte, len(node.content))
if _, err := f.Read(data); err != nil {
t.Fatal(err)
}
if data[1] != 'x' {
t.Errorf("got %c want x", data[1])
}
if _, err := f.Seek(0, 0); err != nil {
t.Fatal(err)
}
node.mu.Lock()
node.mtime = node.mtime.Add(time.Hour)
node.content = bytes.Repeat([]byte{'y'}, 4096)
node.mu.Unlock()
if _, err := f.Stat(); err != nil {
t.Fatal(err)
}
if n, err := f.Read(data); err != nil || len(data) != n {
t.Fatalf("Read: %d, %v", n, err)
}
if data[1] != 'y' {
t.Errorf("got %c want y", data[1])
}
}