mirror of
https://github.com/hanzoai/go-fuse.git
synced 2026-10-30 14:46:18 +00:00
217 lines
4.4 KiB
Go
217 lines
4.4 KiB
Go
// Copyright 2020 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"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoai/go-fuse/v2/fuse"
|
|
"github.com/hanzoai/go-fuse/v2/internal/testutil"
|
|
)
|
|
|
|
type allChildrenNode struct {
|
|
Inode
|
|
|
|
depth int
|
|
}
|
|
|
|
var _ = (NodeLookuper)((*allChildrenNode)(nil))
|
|
var _ = (NodeReaddirer)((*allChildrenNode)(nil))
|
|
|
|
func (n *allChildrenNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
|
if n.depth == 0 {
|
|
return nil, syscall.ENOENT
|
|
}
|
|
stable := StableAttr{
|
|
Mode: syscall.S_IFDIR,
|
|
}
|
|
|
|
if n.depth == 1 {
|
|
stable.Mode = syscall.S_IFREG
|
|
}
|
|
childFN := &allChildrenNode{
|
|
depth: n.depth - 1,
|
|
}
|
|
child := n.NewInode(ctx, childFN, stable)
|
|
return child, 0
|
|
}
|
|
|
|
func (n *allChildrenNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
|
|
var list []fuse.DirEntry
|
|
var m uint32 = syscall.S_IFDIR
|
|
if n.depth == 1 {
|
|
m = syscall.S_IFREG
|
|
}
|
|
for i := 0; i < 100; i++ {
|
|
list = append(list, fuse.DirEntry{
|
|
Name: fmt.Sprintf("%d", i),
|
|
Mode: m,
|
|
})
|
|
}
|
|
return NewListDirStream(list), 0
|
|
}
|
|
|
|
func TestForget(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("must run test as root")
|
|
}
|
|
root := &allChildrenNode{
|
|
depth: 2,
|
|
}
|
|
ttl := 10 * time.Millisecond
|
|
options := &Options{
|
|
FirstAutomaticIno: 1,
|
|
EntryTimeout: &ttl,
|
|
}
|
|
options.Debug = testutil.VerboseTest()
|
|
dir := t.TempDir()
|
|
|
|
rawFS := NewNodeFS(root, options)
|
|
server, err := fuse.NewServer(rawFS, dir, &options.MountOptions)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer server.Unmount()
|
|
go server.Serve()
|
|
if err := server.WaitMount(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
nop := func(path string, info os.FileInfo, err error) error {
|
|
return nil
|
|
}
|
|
|
|
if err := filepath.Walk(dir, nop); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Log("dropping cache")
|
|
if err := os.WriteFile("/proc/sys/vm/drop_caches", []byte("2"), 0644); err != nil {
|
|
|
|
}
|
|
time.Sleep(ttl)
|
|
|
|
bridge := rawFS.(*rawBridge)
|
|
bridge.mu.Lock()
|
|
l := len(bridge.kernelNodeIds)
|
|
bridge.mu.Unlock()
|
|
if l != 1 {
|
|
t.Fatalf("got %d live nodes, want 1", l)
|
|
}
|
|
}
|
|
|
|
type forgetTestRootNode struct {
|
|
Inode
|
|
|
|
mu sync.Mutex
|
|
onForgetCount uint32
|
|
|
|
fileNode *forgetTestSubNode
|
|
dirNode *forgetTestSubNode
|
|
}
|
|
|
|
func (n *forgetTestRootNode) OnForget() {
|
|
atomic.AddUint32(&n.onForgetCount, 1)
|
|
}
|
|
|
|
type forgetTestSubNode struct {
|
|
Inode
|
|
root *forgetTestRootNode
|
|
}
|
|
|
|
func (n *forgetTestSubNode) OnForget() {
|
|
n.root.OnForget()
|
|
}
|
|
|
|
func (n *forgetTestSubNode) OnAdd(ctx context.Context) {
|
|
if !n.IsDir() {
|
|
return
|
|
}
|
|
|
|
ftn := &forgetTestSubNode{root: n.root}
|
|
ch := n.NewPersistentInode(ctx,
|
|
ftn,
|
|
StableAttr{Mode: fuse.S_IFREG})
|
|
|
|
n.root.mu.Lock()
|
|
defer n.root.mu.Unlock()
|
|
n.root.dirNode = n
|
|
n.root.fileNode = ftn
|
|
n.AddChild("child", ch, true)
|
|
}
|
|
|
|
func (n *forgetTestRootNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
|
if name != "subdir" {
|
|
return nil, syscall.ENOENT
|
|
}
|
|
if ch := n.GetChild(name); ch != nil {
|
|
return ch, 0
|
|
}
|
|
|
|
ftn := &forgetTestSubNode{root: n}
|
|
ch := n.NewInode(ctx, ftn,
|
|
StableAttr{Mode: fuse.S_IFDIR})
|
|
return ch, 0
|
|
}
|
|
|
|
func TestOnForget(t *testing.T) {
|
|
root := &forgetTestRootNode{}
|
|
mnt, srv := testMount(t, root, nil)
|
|
|
|
sub := mnt + "/subdir"
|
|
_, err := os.Stat(sub)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root.mu.Lock()
|
|
if root.dirNode == nil {
|
|
t.Fatal("Lookup not triggered")
|
|
}
|
|
if root.fileNode == nil {
|
|
t.Fatal("OnAdd not triggered")
|
|
}
|
|
root.mu.Unlock()
|
|
if root.dirNode.GetChild("child") == nil {
|
|
t.Fatal("child not there")
|
|
}
|
|
if err := syscall.Rmdir(sub); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if root.GetChild("subdir") != nil {
|
|
t.Fatal("rmdir left node")
|
|
}
|
|
|
|
// The kernel issues FORGET immediately, but it takes a bit to
|
|
// process the request.
|
|
time.Sleep(time.Millisecond)
|
|
if got := atomic.LoadUint32(&root.onForgetCount); got != 0 {
|
|
t.Errorf("got count %d, want 0", got)
|
|
}
|
|
|
|
root.fileNode.ForgetPersistent()
|
|
if got := atomic.LoadUint32(&root.onForgetCount); got != 2 {
|
|
t.Errorf("got count %d, want 2", got)
|
|
}
|
|
|
|
if err := srv.Unmount(); err != nil {
|
|
t.Errorf("Unmount: %v", err)
|
|
}
|
|
|
|
// The FORGET processing is not synchronized, so goroutine
|
|
// scheduling may cause it happen after the unmount fully completes.
|
|
time.Sleep(time.Millisecond)
|
|
if got := atomic.LoadUint32(&root.onForgetCount); got != 3 {
|
|
t.Errorf("got count %d, want 3", got)
|
|
}
|
|
}
|