1// Copyright 2016 the Go-FUSE Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package unionfs
6
7import (
8	"fmt"
9	"log"
10	"strings"
11	"time"
12
13	"github.com/hanwen/go-fuse/v2/fuse"
14	"github.com/hanwen/go-fuse/v2/fuse/nodefs"
15	"github.com/hanwen/go-fuse/v2/fuse/pathfs"
16)
17
18const _XATTRSEP = "@XATTR@"
19
20type attrResponse struct {
21	*fuse.Attr
22	fuse.Status
23}
24
25type xattrResponse struct {
26	data []byte
27	fuse.Status
28}
29
30type dirResponse struct {
31	entries []fuse.DirEntry
32	fuse.Status
33}
34
35type linkResponse struct {
36	linkContent string
37	fuse.Status
38}
39
40// Caches filesystem metadata.
41type cachingFileSystem struct {
42	pathfs.FileSystem
43
44	attributes *TimedCache
45	dirs       *TimedCache
46	links      *TimedCache
47	xattr      *TimedCache
48}
49
50func readDir(fs pathfs.FileSystem, name string) *dirResponse {
51	origStream, code := fs.OpenDir(name, nil)
52
53	r := &dirResponse{nil, code}
54	if !code.Ok() {
55		return r
56	}
57	r.entries = origStream
58	return r
59}
60
61func getAttr(fs pathfs.FileSystem, name string) *attrResponse {
62	a, code := fs.GetAttr(name, nil)
63	return &attrResponse{
64		Attr:   a,
65		Status: code,
66	}
67}
68
69func getXAttr(fs pathfs.FileSystem, nameAttr string) *xattrResponse {
70	ns := strings.SplitN(nameAttr, _XATTRSEP, 2)
71	a, code := fs.GetXAttr(ns[0], ns[1], nil)
72	return &xattrResponse{
73		data:   a,
74		Status: code,
75	}
76}
77
78func readLink(fs pathfs.FileSystem, name string) *linkResponse {
79	a, code := fs.Readlink(name, nil)
80	return &linkResponse{
81		linkContent: a,
82		Status:      code,
83	}
84}
85
86func NewCachingFileSystem(fs pathfs.FileSystem, ttl time.Duration) pathfs.FileSystem {
87	c := new(cachingFileSystem)
88	c.FileSystem = fs
89	c.attributes = NewTimedCache(func(n string) (interface{}, bool) {
90		a := getAttr(fs, n)
91		return a, a.Ok()
92	}, ttl)
93	c.dirs = NewTimedCache(func(n string) (interface{}, bool) {
94		d := readDir(fs, n)
95		return d, d.Ok()
96	}, ttl)
97	c.links = NewTimedCache(func(n string) (interface{}, bool) {
98		l := readLink(fs, n)
99		return l, l.Ok()
100	}, ttl)
101	c.xattr = NewTimedCache(func(n string) (interface{}, bool) {
102		l := getXAttr(fs, n)
103		return l, l.Ok()
104	}, ttl)
105	return c
106}
107
108func (fs *cachingFileSystem) DropCache() {
109	for _, c := range []*TimedCache{fs.attributes, fs.dirs, fs.links, fs.xattr} {
110		c.DropAll(nil)
111	}
112}
113
114func (fs *cachingFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
115	if name == _DROP_CACHE {
116		return &fuse.Attr{
117			Mode: fuse.S_IFREG | 0777,
118		}, fuse.OK
119	}
120
121	r := fs.attributes.Get(name).(*attrResponse)
122	return r.Attr, r.Status
123}
124
125func (fs *cachingFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
126	key := name + _XATTRSEP + attr
127	r := fs.xattr.Get(key).(*xattrResponse)
128	return r.data, r.Status
129}
130
131func (fs *cachingFileSystem) Readlink(name string, context *fuse.Context) (string, fuse.Status) {
132	r := fs.links.Get(name).(*linkResponse)
133	return r.linkContent, r.Status
134}
135
136func (fs *cachingFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
137	r := fs.dirs.Get(name).(*dirResponse)
138	return r.entries, r.Status
139}
140
141func (fs *cachingFileSystem) String() string {
142	return fmt.Sprintf("cachingFileSystem(%v)", fs.FileSystem)
143}
144
145func (fs *cachingFileSystem) Open(name string, flags uint32, context *fuse.Context) (f nodefs.File, status fuse.Status) {
146	if flags&fuse.O_ANYWRITE != 0 && name == _DROP_CACHE {
147		log.Println("Dropping cache for", fs)
148		fs.DropCache()
149	}
150	return fs.FileSystem.Open(name, flags, context)
151}
152