use cachingfs
This commit is contained in:
parent
892908fcf5
commit
61b665af13
10 changed files with 1963 additions and 2 deletions
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{}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue