use cachingfs
This commit is contained in:
parent
892908fcf5
commit
61b665af13
10 changed files with 1963 additions and 2 deletions
5
Gopkg.lock
generated
5
Gopkg.lock
generated
|
|
@ -14,7 +14,8 @@
|
|||
"fuse",
|
||||
"fuse/nodefs",
|
||||
"fuse/pathfs",
|
||||
"splice"
|
||||
"splice",
|
||||
"unionfs"
|
||||
]
|
||||
revision = "a9ddcb8a4b609500fc59c89ccc9ee05f00a5fefd"
|
||||
|
||||
|
|
@ -60,6 +61,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "9bee1a4379bcd4b9176976a3cedac4a5c846c42ebdc54c623d3fb551c6921aa9"
|
||||
inputs-digest = "3446597fa9ac971d950ce0953fbc4e46796f213d08a7781d54fe17858e2af33b"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/hanwen/go-fuse/fuse"
|
||||
"github.com/hanwen/go-fuse/fuse/nodefs"
|
||||
"github.com/hanwen/go-fuse/fuse/pathfs"
|
||||
"github.com/hanwen/go-fuse/unionfs"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ func NewWsFs(opts WsFsOpts, req chan RequestCallback, closer chan bool) *pathfs.
|
|||
if opts.ReadOnly {
|
||||
fs = pathfs.NewReadonlyFileSystem(fs)
|
||||
}
|
||||
fs = unionfs.NewCachingFileSystem(fs, 1*time.Second)
|
||||
return pathfs.NewPathNodeFs(fs, nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
452
vendor/github.com/hanwen/go-fuse/unionfs/autounion.go
generated
vendored
Normal file
452
vendor/github.com/hanwen/go-fuse/unionfs/autounion.go
generated
vendored
Normal file
|
|
@ -0,0 +1,452 @@
|
|||
// Copyright 2016 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 unionfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
"github.com/hanwen/go-fuse/fuse/nodefs"
|
||||
"github.com/hanwen/go-fuse/fuse/pathfs"
|
||||
)
|
||||
|
||||
type knownFs struct {
|
||||
unionFS pathfs.FileSystem
|
||||
nodeFS *pathfs.PathNodeFs
|
||||
}
|
||||
|
||||
// Creates unions for all files under a given directory,
|
||||
// walking the tree and looking for directories D which have a
|
||||
// D/READONLY symlink.
|
||||
//
|
||||
// A union for A/B/C will placed under directory A-B-C.
|
||||
type autoUnionFs struct {
|
||||
pathfs.FileSystem
|
||||
debug bool
|
||||
|
||||
lock sync.RWMutex
|
||||
zombies map[string]bool
|
||||
knownFileSystems map[string]knownFs
|
||||
nameRootMap map[string]string
|
||||
root string
|
||||
|
||||
nodeFs *pathfs.PathNodeFs
|
||||
options *AutoUnionFsOptions
|
||||
}
|
||||
|
||||
type AutoUnionFsOptions struct {
|
||||
UnionFsOptions
|
||||
|
||||
nodefs.Options
|
||||
pathfs.PathNodeFsOptions
|
||||
|
||||
// If set, run updateKnownFses() after mounting.
|
||||
UpdateOnMount bool
|
||||
|
||||
// If set hides the _READONLY file.
|
||||
HideReadonly bool
|
||||
|
||||
// Expose this version in /status/gounionfs_version
|
||||
Version string
|
||||
}
|
||||
|
||||
const (
|
||||
_READONLY = "READONLY"
|
||||
_STATUS = "status"
|
||||
_CONFIG = "config"
|
||||
_DEBUG = "debug"
|
||||
_ROOT = "root"
|
||||
_VERSION = "gounionfs_version"
|
||||
_SCAN_CONFIG = ".scan_config"
|
||||
)
|
||||
|
||||
func NewAutoUnionFs(directory string, options AutoUnionFsOptions) pathfs.FileSystem {
|
||||
if options.HideReadonly {
|
||||
options.HiddenFiles = append(options.HiddenFiles, _READONLY)
|
||||
}
|
||||
a := &autoUnionFs{
|
||||
knownFileSystems: make(map[string]knownFs),
|
||||
nameRootMap: make(map[string]string),
|
||||
zombies: make(map[string]bool),
|
||||
options: &options,
|
||||
FileSystem: pathfs.NewDefaultFileSystem(),
|
||||
}
|
||||
|
||||
directory, err := filepath.Abs(directory)
|
||||
if err != nil {
|
||||
panic("filepath.Abs returned err")
|
||||
}
|
||||
a.root = directory
|
||||
return a
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) String() string {
|
||||
return fmt.Sprintf("autoUnionFs(%s)", fs.root)
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) OnMount(nodeFs *pathfs.PathNodeFs) {
|
||||
fs.nodeFs = nodeFs
|
||||
if fs.options.UpdateOnMount {
|
||||
time.AfterFunc(100*time.Millisecond, func() { fs.updateKnownFses() })
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) addAutomaticFs(roots []string) {
|
||||
relative := strings.TrimLeft(strings.Replace(roots[0], fs.root, "", -1), "/")
|
||||
name := strings.Replace(relative, "/", "-", -1)
|
||||
|
||||
if fs.getUnionFs(name) == nil {
|
||||
fs.addFs(name, roots)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) createFs(name string, roots []string) fuse.Status {
|
||||
fs.lock.Lock()
|
||||
defer fs.lock.Unlock()
|
||||
|
||||
if fs.zombies[name] {
|
||||
log.Printf("filesystem named %q is being removed", name)
|
||||
return fuse.EBUSY
|
||||
}
|
||||
|
||||
for workspace, root := range fs.nameRootMap {
|
||||
if root == roots[0] && workspace != name {
|
||||
log.Printf("Already have a union FS for directory %s in workspace %s",
|
||||
roots[0], workspace)
|
||||
return fuse.EBUSY
|
||||
}
|
||||
}
|
||||
|
||||
known := fs.knownFileSystems[name]
|
||||
if known.unionFS != nil {
|
||||
log.Println("Already have a workspace:", name)
|
||||
return fuse.EBUSY
|
||||
}
|
||||
|
||||
ufs, err := NewUnionFsFromRoots(roots, &fs.options.UnionFsOptions, true)
|
||||
if err != nil {
|
||||
log.Println("Could not create UnionFs:", err)
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
log.Printf("Adding workspace %v for roots %v", name, ufs.String())
|
||||
nfs := pathfs.NewPathNodeFs(ufs, &fs.options.PathNodeFsOptions)
|
||||
code := fs.nodeFs.Mount(name, nfs.Root(), &fs.options.Options)
|
||||
if code.Ok() {
|
||||
fs.knownFileSystems[name] = knownFs{
|
||||
ufs,
|
||||
nfs,
|
||||
}
|
||||
fs.nameRootMap[name] = roots[0]
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) rmFs(name string) (code fuse.Status) {
|
||||
fs.lock.Lock()
|
||||
defer fs.lock.Unlock()
|
||||
|
||||
if fs.zombies[name] {
|
||||
return fuse.ENOENT
|
||||
}
|
||||
|
||||
known := fs.knownFileSystems[name]
|
||||
if known.unionFS == nil {
|
||||
return fuse.ENOENT
|
||||
}
|
||||
|
||||
root := fs.nameRootMap[name]
|
||||
delete(fs.knownFileSystems, name)
|
||||
delete(fs.nameRootMap, name)
|
||||
fs.zombies[name] = true
|
||||
fs.lock.Unlock()
|
||||
code = fs.nodeFs.Unmount(name)
|
||||
|
||||
fs.lock.Lock()
|
||||
delete(fs.zombies, name)
|
||||
if !code.Ok() {
|
||||
// Reinstate.
|
||||
log.Printf("Unmount failed for %s. Code %v", name, code)
|
||||
fs.knownFileSystems[name] = known
|
||||
fs.nameRootMap[name] = root
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) addFs(name string, roots []string) (code fuse.Status) {
|
||||
if name == _CONFIG || name == _STATUS || name == _SCAN_CONFIG {
|
||||
return fuse.EINVAL
|
||||
}
|
||||
return fs.createFs(name, roots)
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) getRoots(path string) []string {
|
||||
ro := filepath.Join(path, _READONLY)
|
||||
fi, err := os.Lstat(ro)
|
||||
fiDir, errDir := os.Stat(ro)
|
||||
if err != nil || errDir != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink != 0 && fiDir.IsDir() {
|
||||
// TODO - should recurse and chain all READONLYs
|
||||
// together.
|
||||
return []string{path, ro}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) visit(path string, fi os.FileInfo, err error) error {
|
||||
if fi != nil && fi.IsDir() {
|
||||
roots := fs.getRoots(path)
|
||||
if roots != nil {
|
||||
fs.addAutomaticFs(roots)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) updateKnownFses() {
|
||||
// We unroll the first level of entries in the root manually in order
|
||||
// to allow symbolic links on that level.
|
||||
directoryEntries, err := ioutil.ReadDir(fs.root)
|
||||
if err == nil {
|
||||
for _, dir := range directoryEntries {
|
||||
if dir.IsDir() || dir.Mode()&os.ModeSymlink != 0 {
|
||||
path := filepath.Join(fs.root, dir.Name())
|
||||
dir, _ = os.Stat(path)
|
||||
fs.visit(path, dir, nil)
|
||||
filepath.Walk(path,
|
||||
func(path string, fi os.FileInfo, err error) error {
|
||||
return fs.visit(path, fi, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) Readlink(path string, context *fuse.Context) (out string, code fuse.Status) {
|
||||
comps := strings.Split(path, string(filepath.Separator))
|
||||
if comps[0] == _STATUS && comps[1] == _ROOT {
|
||||
return fs.root, fuse.OK
|
||||
}
|
||||
|
||||
if comps[0] != _CONFIG {
|
||||
return "", fuse.ENOENT
|
||||
}
|
||||
|
||||
name := comps[1]
|
||||
|
||||
fs.lock.RLock()
|
||||
defer fs.lock.RUnlock()
|
||||
|
||||
root, ok := fs.nameRootMap[name]
|
||||
if ok {
|
||||
return root, fuse.OK
|
||||
}
|
||||
return "", fuse.ENOENT
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) getUnionFs(name string) pathfs.FileSystem {
|
||||
fs.lock.RLock()
|
||||
defer fs.lock.RUnlock()
|
||||
return fs.knownFileSystems[name].unionFS
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) {
|
||||
comps := strings.Split(linkName, "/")
|
||||
if len(comps) != 2 {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
if comps[0] == _CONFIG {
|
||||
roots := fs.getRoots(pointedTo)
|
||||
if roots == nil {
|
||||
return fuse.Status(syscall.ENOTDIR)
|
||||
}
|
||||
|
||||
name := comps[1]
|
||||
return fs.addFs(name, roots)
|
||||
}
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) Unlink(path string, context *fuse.Context) (code fuse.Status) {
|
||||
comps := strings.Split(path, "/")
|
||||
if len(comps) != 2 {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
if comps[0] == _CONFIG && comps[1] != _SCAN_CONFIG {
|
||||
code = fs.rmFs(comps[1])
|
||||
} else {
|
||||
code = fuse.ENOENT
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// Must define this, because ENOSYS will suspend all GetXAttr calls.
|
||||
func (fs *autoUnionFs) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
|
||||
return nil, fuse.ENOATTR
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) GetAttr(path string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
|
||||
a := &fuse.Attr{
|
||||
Owner: *fuse.CurrentOwner(),
|
||||
}
|
||||
if path == "" || path == _CONFIG || path == _STATUS {
|
||||
a.Mode = fuse.S_IFDIR | 0755
|
||||
return a, fuse.OK
|
||||
}
|
||||
|
||||
if path == filepath.Join(_STATUS, _VERSION) {
|
||||
a.Mode = fuse.S_IFREG | 0644
|
||||
a.Size = uint64(len(fs.options.Version))
|
||||
return a, fuse.OK
|
||||
}
|
||||
|
||||
if path == filepath.Join(_STATUS, _DEBUG) {
|
||||
a.Mode = fuse.S_IFREG | 0644
|
||||
a.Size = uint64(len(fs.DebugData()))
|
||||
return a, fuse.OK
|
||||
}
|
||||
|
||||
if path == filepath.Join(_STATUS, _ROOT) {
|
||||
a.Mode = syscall.S_IFLNK | 0644
|
||||
return a, fuse.OK
|
||||
}
|
||||
|
||||
if path == filepath.Join(_CONFIG, _SCAN_CONFIG) {
|
||||
a.Mode = fuse.S_IFREG | 0644
|
||||
return a, fuse.OK
|
||||
}
|
||||
comps := strings.Split(path, string(filepath.Separator))
|
||||
|
||||
if len(comps) > 1 && comps[0] == _CONFIG {
|
||||
fs := fs.getUnionFs(comps[1])
|
||||
|
||||
if fs == nil {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
a.Mode = syscall.S_IFLNK | 0644
|
||||
return a, fuse.OK
|
||||
}
|
||||
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) StatusDir() (stream []fuse.DirEntry, status fuse.Status) {
|
||||
stream = make([]fuse.DirEntry, 0, 10)
|
||||
stream = []fuse.DirEntry{
|
||||
{Name: _VERSION, Mode: fuse.S_IFREG | 0644},
|
||||
{Name: _DEBUG, Mode: fuse.S_IFREG | 0644},
|
||||
{Name: _ROOT, Mode: syscall.S_IFLNK | 0644},
|
||||
}
|
||||
return stream, fuse.OK
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) DebugData() string {
|
||||
conn := fs.nodeFs.Connector()
|
||||
if conn.Server() == nil {
|
||||
return "autoUnionFs.mountState not set"
|
||||
}
|
||||
setting := conn.Server().KernelSettings()
|
||||
msg := fmt.Sprintf(
|
||||
"Version: %v\n"+
|
||||
"Bufferpool: %v\n"+
|
||||
"Kernel: %v\n",
|
||||
fs.options.Version,
|
||||
conn.Server().DebugData(),
|
||||
&setting)
|
||||
|
||||
if conn != nil {
|
||||
msg += fmt.Sprintf("Live inodes: %d\n", conn.InodeHandleCount())
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) Open(path string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) {
|
||||
if path == filepath.Join(_STATUS, _DEBUG) {
|
||||
if flags&fuse.O_ANYWRITE != 0 {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
|
||||
return nodefs.NewDataFile([]byte(fs.DebugData())), fuse.OK
|
||||
}
|
||||
if path == filepath.Join(_STATUS, _VERSION) {
|
||||
if flags&fuse.O_ANYWRITE != 0 {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
return nodefs.NewDataFile([]byte(fs.options.Version)), fuse.OK
|
||||
}
|
||||
if path == filepath.Join(_CONFIG, _SCAN_CONFIG) {
|
||||
if flags&fuse.O_ANYWRITE != 0 {
|
||||
fs.updateKnownFses()
|
||||
}
|
||||
return nodefs.NewDevNullFile(), fuse.OK
|
||||
}
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) Truncate(name string, offset uint64, context *fuse.Context) (code fuse.Status) {
|
||||
if name != filepath.Join(_CONFIG, _SCAN_CONFIG) {
|
||||
log.Println("Huh? Truncating unsupported write file", name)
|
||||
return fuse.EPERM
|
||||
}
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
|
||||
switch name {
|
||||
case _STATUS:
|
||||
return fs.StatusDir()
|
||||
case _CONFIG:
|
||||
case "/":
|
||||
name = ""
|
||||
case "":
|
||||
default:
|
||||
log.Printf("Argh! Don't know how to list dir %v", name)
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
fs.lock.RLock()
|
||||
defer fs.lock.RUnlock()
|
||||
|
||||
stream = make([]fuse.DirEntry, 0, len(fs.knownFileSystems)+5)
|
||||
if name == _CONFIG {
|
||||
for k := range fs.knownFileSystems {
|
||||
stream = append(stream, fuse.DirEntry{
|
||||
Name: k,
|
||||
Mode: syscall.S_IFLNK | 0644,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
stream = append(stream, fuse.DirEntry{
|
||||
Name: _CONFIG,
|
||||
Mode: uint32(fuse.S_IFDIR | 0755),
|
||||
},
|
||||
fuse.DirEntry{
|
||||
Name: _STATUS,
|
||||
Mode: uint32(fuse.S_IFDIR | 0755),
|
||||
})
|
||||
}
|
||||
return stream, status
|
||||
}
|
||||
|
||||
func (fs *autoUnionFs) StatFs(name string) *fuse.StatfsOut {
|
||||
return &fuse.StatfsOut{}
|
||||
}
|
||||
151
vendor/github.com/hanwen/go-fuse/unionfs/cachingfs.go
generated
vendored
Normal file
151
vendor/github.com/hanwen/go-fuse/unionfs/cachingfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2016 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 unionfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
"github.com/hanwen/go-fuse/fuse/nodefs"
|
||||
"github.com/hanwen/go-fuse/fuse/pathfs"
|
||||
)
|
||||
|
||||
const _XATTRSEP = "@XATTR@"
|
||||
|
||||
type attrResponse struct {
|
||||
*fuse.Attr
|
||||
fuse.Status
|
||||
}
|
||||
|
||||
type xattrResponse struct {
|
||||
data []byte
|
||||
fuse.Status
|
||||
}
|
||||
|
||||
type dirResponse struct {
|
||||
entries []fuse.DirEntry
|
||||
fuse.Status
|
||||
}
|
||||
|
||||
type linkResponse struct {
|
||||
linkContent string
|
||||
fuse.Status
|
||||
}
|
||||
|
||||
// Caches filesystem metadata.
|
||||
type cachingFileSystem struct {
|
||||
pathfs.FileSystem
|
||||
|
||||
attributes *TimedCache
|
||||
dirs *TimedCache
|
||||
links *TimedCache
|
||||
xattr *TimedCache
|
||||
}
|
||||
|
||||
func readDir(fs pathfs.FileSystem, name string) *dirResponse {
|
||||
origStream, code := fs.OpenDir(name, nil)
|
||||
|
||||
r := &dirResponse{nil, code}
|
||||
if !code.Ok() {
|
||||
return r
|
||||
}
|
||||
r.entries = origStream
|
||||
return r
|
||||
}
|
||||
|
||||
func getAttr(fs pathfs.FileSystem, name string) *attrResponse {
|
||||
a, code := fs.GetAttr(name, nil)
|
||||
return &attrResponse{
|
||||
Attr: a,
|
||||
Status: code,
|
||||
}
|
||||
}
|
||||
|
||||
func getXAttr(fs pathfs.FileSystem, nameAttr string) *xattrResponse {
|
||||
ns := strings.SplitN(nameAttr, _XATTRSEP, 2)
|
||||
a, code := fs.GetXAttr(ns[0], ns[1], nil)
|
||||
return &xattrResponse{
|
||||
data: a,
|
||||
Status: code,
|
||||
}
|
||||
}
|
||||
|
||||
func readLink(fs pathfs.FileSystem, name string) *linkResponse {
|
||||
a, code := fs.Readlink(name, nil)
|
||||
return &linkResponse{
|
||||
linkContent: a,
|
||||
Status: code,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCachingFileSystem(fs pathfs.FileSystem, ttl time.Duration) pathfs.FileSystem {
|
||||
c := new(cachingFileSystem)
|
||||
c.FileSystem = fs
|
||||
c.attributes = NewTimedCache(func(n string) (interface{}, bool) {
|
||||
a := getAttr(fs, n)
|
||||
return a, a.Ok()
|
||||
}, ttl)
|
||||
c.dirs = NewTimedCache(func(n string) (interface{}, bool) {
|
||||
d := readDir(fs, n)
|
||||
return d, d.Ok()
|
||||
}, ttl)
|
||||
c.links = NewTimedCache(func(n string) (interface{}, bool) {
|
||||
l := readLink(fs, n)
|
||||
return l, l.Ok()
|
||||
}, ttl)
|
||||
c.xattr = NewTimedCache(func(n string) (interface{}, bool) {
|
||||
l := getXAttr(fs, n)
|
||||
return l, l.Ok()
|
||||
}, ttl)
|
||||
return c
|
||||
}
|
||||
|
||||
func (fs *cachingFileSystem) DropCache() {
|
||||
for _, c := range []*TimedCache{fs.attributes, fs.dirs, fs.links, fs.xattr} {
|
||||
c.DropAll(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *cachingFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
|
||||
if name == _DROP_CACHE {
|
||||
return &fuse.Attr{
|
||||
Mode: fuse.S_IFREG | 0777,
|
||||
}, fuse.OK
|
||||
}
|
||||
|
||||
r := fs.attributes.Get(name).(*attrResponse)
|
||||
return r.Attr, r.Status
|
||||
}
|
||||
|
||||
func (fs *cachingFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
|
||||
key := name + _XATTRSEP + attr
|
||||
r := fs.xattr.Get(key).(*xattrResponse)
|
||||
return r.data, r.Status
|
||||
}
|
||||
|
||||
func (fs *cachingFileSystem) Readlink(name string, context *fuse.Context) (string, fuse.Status) {
|
||||
r := fs.links.Get(name).(*linkResponse)
|
||||
return r.linkContent, r.Status
|
||||
}
|
||||
|
||||
func (fs *cachingFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
|
||||
r := fs.dirs.Get(name).(*dirResponse)
|
||||
return r.entries, r.Status
|
||||
}
|
||||
|
||||
func (fs *cachingFileSystem) String() string {
|
||||
return fmt.Sprintf("cachingFileSystem(%v)", fs.FileSystem)
|
||||
}
|
||||
|
||||
func (fs *cachingFileSystem) Open(name string, flags uint32, context *fuse.Context) (f nodefs.File, status fuse.Status) {
|
||||
if flags&fuse.O_ANYWRITE != 0 && name == _DROP_CACHE {
|
||||
log.Println("Dropping cache for", fs)
|
||||
fs.DropCache()
|
||||
}
|
||||
return fs.FileSystem.Open(name, flags, context)
|
||||
}
|
||||
36
vendor/github.com/hanwen/go-fuse/unionfs/create.go
generated
vendored
Normal file
36
vendor/github.com/hanwen/go-fuse/unionfs/create.go
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2016 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 unionfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse/pathfs"
|
||||
)
|
||||
|
||||
func NewUnionFsFromRoots(roots []string, opts *UnionFsOptions, roCaching bool) (pathfs.FileSystem, error) {
|
||||
fses := make([]pathfs.FileSystem, 0)
|
||||
for i, r := range roots {
|
||||
var fs pathfs.FileSystem
|
||||
fi, err := os.Stat(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
fs = pathfs.NewLoopbackFileSystem(r)
|
||||
}
|
||||
if fs == nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
if i > 0 && roCaching {
|
||||
fs = NewCachingFileSystem(fs, 0)
|
||||
}
|
||||
|
||||
fses = append(fses, fs)
|
||||
}
|
||||
|
||||
return NewUnionFs(fses, *opts)
|
||||
}
|
||||
125
vendor/github.com/hanwen/go-fuse/unionfs/dircache.go
generated
vendored
Normal file
125
vendor/github.com/hanwen/go-fuse/unionfs/dircache.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2016 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 unionfs
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
"github.com/hanwen/go-fuse/fuse/pathfs"
|
||||
)
|
||||
|
||||
// newDirnameMap reads the contents of the given directory. On error,
|
||||
// returns a nil map. This forces reloads in the dirCache until we
|
||||
// succeed.
|
||||
func newDirnameMap(fs pathfs.FileSystem, dir string) map[string]bool {
|
||||
stream, code := fs.OpenDir(dir, nil)
|
||||
if code == fuse.ENOENT {
|
||||
// The directory not existing is not an error.
|
||||
return map[string]bool{}
|
||||
}
|
||||
|
||||
if !code.Ok() {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make(map[string]bool)
|
||||
for _, e := range stream {
|
||||
if e.Mode&fuse.S_IFREG != 0 {
|
||||
result[e.Name] = true
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// dirCache caches names in a directory for some time.
|
||||
//
|
||||
// If called when the cache is expired, the filenames are read afresh in
|
||||
// the background.
|
||||
type dirCache struct {
|
||||
dir string
|
||||
ttl time.Duration
|
||||
fs pathfs.FileSystem
|
||||
// Protects data below.
|
||||
lock sync.RWMutex
|
||||
|
||||
// If nil, you may call refresh() to schedule a new one.
|
||||
names map[string]bool
|
||||
updateRunning bool
|
||||
}
|
||||
|
||||
func (c *dirCache) setMap(newMap map[string]bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.names = newMap
|
||||
c.updateRunning = false
|
||||
_ = time.AfterFunc(c.ttl,
|
||||
func() { c.DropCache() })
|
||||
}
|
||||
|
||||
func (c *dirCache) DropCache() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.names = nil
|
||||
}
|
||||
|
||||
// Try to refresh: if another update is already running, do nothing,
|
||||
// otherwise, read the directory and set it.
|
||||
func (c *dirCache) maybeRefresh() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.updateRunning {
|
||||
return
|
||||
}
|
||||
c.updateRunning = true
|
||||
go func() {
|
||||
newmap := newDirnameMap(c.fs, c.dir)
|
||||
c.setMap(newmap)
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *dirCache) RemoveEntry(name string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.names == nil {
|
||||
go c.maybeRefresh()
|
||||
return
|
||||
}
|
||||
|
||||
delete(c.names, name)
|
||||
}
|
||||
|
||||
func (c *dirCache) AddEntry(name string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.names == nil {
|
||||
go c.maybeRefresh()
|
||||
return
|
||||
}
|
||||
|
||||
c.names[name] = true
|
||||
}
|
||||
|
||||
func newDirCache(fs pathfs.FileSystem, dir string, ttl time.Duration) *dirCache {
|
||||
dc := new(dirCache)
|
||||
dc.dir = dir
|
||||
dc.fs = fs
|
||||
dc.ttl = ttl
|
||||
return dc
|
||||
}
|
||||
|
||||
func (c *dirCache) HasEntry(name string) (mapPresent bool, found bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
if c.names == nil {
|
||||
go c.maybeRefresh()
|
||||
return false, false
|
||||
}
|
||||
|
||||
return true, c.names[name]
|
||||
}
|
||||
111
vendor/github.com/hanwen/go-fuse/unionfs/timedcache.go
generated
vendored
Normal file
111
vendor/github.com/hanwen/go-fuse/unionfs/timedcache.go
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2016 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 unionfs
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
data interface{}
|
||||
|
||||
// expiry is the absolute timestamp of the expiry.
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
// TimedIntCache caches the result of fetch() for some time. It is
|
||||
// thread-safe. Calls of fetch() do no happen inside a critical
|
||||
// section, so when multiple concurrent Get()s happen for the same
|
||||
// key, multiple fetch() calls may be issued for the same key.
|
||||
type TimedCacheFetcher func(name string) (value interface{}, cacheable bool)
|
||||
type TimedCache struct {
|
||||
fetch TimedCacheFetcher
|
||||
|
||||
// ttl is the duration of the cache.
|
||||
ttl time.Duration
|
||||
|
||||
cacheMapMutex sync.RWMutex
|
||||
cacheMap map[string]*cacheEntry
|
||||
|
||||
PurgeTimer *time.Timer
|
||||
}
|
||||
|
||||
// Creates a new cache with the given TTL. If TTL <= 0, the caching is
|
||||
// indefinite.
|
||||
func NewTimedCache(fetcher TimedCacheFetcher, ttl time.Duration) *TimedCache {
|
||||
l := new(TimedCache)
|
||||
l.ttl = ttl
|
||||
l.fetch = fetcher
|
||||
l.cacheMap = make(map[string]*cacheEntry)
|
||||
return l
|
||||
}
|
||||
|
||||
func (c *TimedCache) Get(name string) interface{} {
|
||||
c.cacheMapMutex.RLock()
|
||||
info, ok := c.cacheMap[name]
|
||||
c.cacheMapMutex.RUnlock()
|
||||
|
||||
valid := ok && (c.ttl <= 0 || info.expiry.After(time.Now()))
|
||||
if valid {
|
||||
return info.data
|
||||
}
|
||||
return c.GetFresh(name)
|
||||
}
|
||||
|
||||
func (c *TimedCache) Set(name string, val interface{}) {
|
||||
c.cacheMapMutex.Lock()
|
||||
defer c.cacheMapMutex.Unlock()
|
||||
|
||||
c.cacheMap[name] = &cacheEntry{
|
||||
data: val,
|
||||
expiry: time.Now().Add(c.ttl),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TimedCache) DropEntry(name string) {
|
||||
c.cacheMapMutex.Lock()
|
||||
defer c.cacheMapMutex.Unlock()
|
||||
|
||||
delete(c.cacheMap, name)
|
||||
}
|
||||
|
||||
func (c *TimedCache) GetFresh(name string) interface{} {
|
||||
data, ok := c.fetch(name)
|
||||
if ok {
|
||||
c.Set(name, data)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Drop all expired entries.
|
||||
func (c *TimedCache) Purge() {
|
||||
keys := make([]string, 0, len(c.cacheMap))
|
||||
now := time.Now()
|
||||
|
||||
c.cacheMapMutex.Lock()
|
||||
defer c.cacheMapMutex.Unlock()
|
||||
for k, v := range c.cacheMap {
|
||||
if now.After(v.expiry) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
for _, k := range keys {
|
||||
delete(c.cacheMap, k)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TimedCache) DropAll(names []string) {
|
||||
c.cacheMapMutex.Lock()
|
||||
defer c.cacheMapMutex.Unlock()
|
||||
|
||||
if names == nil {
|
||||
c.cacheMap = make(map[string]*cacheEntry, len(c.cacheMap))
|
||||
} else {
|
||||
for _, nm := range names {
|
||||
delete(c.cacheMap, nm)
|
||||
}
|
||||
}
|
||||
}
|
||||
1032
vendor/github.com/hanwen/go-fuse/unionfs/unionfs.go
generated
vendored
Normal file
1032
vendor/github.com/hanwen/go-fuse/unionfs/unionfs.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
37
vendor/github.com/hanwen/go-fuse/unionfs/unionfs_xattr_test_darwin.go
generated
vendored
Normal file
37
vendor/github.com/hanwen/go-fuse/unionfs/unionfs_xattr_test_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2016 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 unionfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Darwin doesn't have support for syscall.Getxattr() so we pull it into its own file and implement it by hand on Darwin.
|
||||
func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p2 unsafe.Pointer
|
||||
if len(dest) > 0 {
|
||||
_p2 = unsafe.Pointer(&dest[0])
|
||||
} else {
|
||||
var _zero uintptr
|
||||
_p2 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), 0, 0)
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
14
vendor/github.com/hanwen/go-fuse/unionfs/unionfs_xattr_test_linux.go
generated
vendored
Normal file
14
vendor/github.com/hanwen/go-fuse/unionfs/unionfs_xattr_test_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2016 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 unionfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Darwin doesn't have support for syscall.Getxattr() so we pull it into its own file and implement it by hand on Darwin.
|
||||
func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
|
||||
return syscall.Getxattr(path, attr, dest)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue