mirror of
https://github.com/hanzoai/go-fuse.git
synced 2026-10-30 14:46:18 +00:00
331 lines
7.1 KiB
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])
|
|
}
|
|
}
|