Commit 176ecae2 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys Committed by Han-Wen Nienhuys

fuse: support symlink caching

Adds fuse.Options.EnableSymlinkCaching to enable cached
symlinks. Updates in GETATTR results do not trigger new READLINK
calls, so this breaks backward compatibility for file systems that
have auto-updating symlinks (e.g. a symlink pointing to the HEAD
revision of a remote git repository)

Change-Id: Ifcbcd75e
parent d499a8d0
......@@ -9,11 +9,15 @@ import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"sync"
"syscall"
"testing"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type keepCacheFile struct {
......@@ -134,3 +138,103 @@ func TestKeepCache(t *testing.T) {
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] {
log.Printf("got %q want %q", l, want[:1])
}
if c := link.count(); c != 2 {
t.Errorf("got %d want 2", c)
}
}
......@@ -223,6 +223,11 @@ type MountOptions struct {
// you must implement the GetLk/SetLk/SetLkw methods.
EnableLocks bool
// If set, the kernel caches all Readlink return values. The
// filesystem must use content notification to force the
// kernel to issue a new Readlink call.
EnableSymlinkCaching bool
// If set, ask kernel not to do automatic data cache invalidation.
// The filesystem is fully responsible for invalidating data cache.
ExplicitDataCacheControl bool
......
......@@ -101,7 +101,9 @@ func doInit(server *Server, req *request) {
if server.opts.EnableLocks {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
}
if server.opts.EnableSymlinkCaching {
server.kernelSettings.Flags |= CAP_CACHE_SYMLINKS
}
if server.opts.EnableAcl {
server.kernelSettings.Flags |= CAP_POSIX_ACL
}
......
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