Commit c9b1b570 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Clean up cache handling:

* Always strip trailing '/' from keys

* UnionFs.getBranchResult() to cut down on type casts.

* Introduct UnionFs.promoteDirsTo(), updating the branch cache for
  each parent directory too.

* On Chmod, update the branchcache too.

* On file release, drop the cache entry, which probably has incorrect
  file size.
parent fb4ec9d0
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"sync" "sync"
"strings"
) )
// TODO(hanwen): is md5 sufficiently fast? // TODO(hanwen): is md5 sufficiently fast?
...@@ -128,10 +129,14 @@ func (me *UnionFs) isDeleted(name string) bool { ...@@ -128,10 +129,14 @@ func (me *UnionFs) isDeleted(name string) bool {
return fi != nil return fi != nil
} }
func (me *UnionFs) getBranch(name string) int { func (me *UnionFs) getBranchResult(name string) getBranchResult {
name = stripSlash(name)
r := me.branchCache.Get(name) r := me.branchCache.Get(name)
result := r.(getBranchResult) return r.(getBranchResult)
return result.branch }
func (me *UnionFs) getBranch(name string) int {
return me.getBranchResult(name).branch
} }
type getBranchResult struct { type getBranchResult struct {
...@@ -141,12 +146,15 @@ type getBranchResult struct { ...@@ -141,12 +146,15 @@ type getBranchResult struct {
} }
func (me *UnionFs) getBranchAttrNoCache(name string) getBranchResult { func (me *UnionFs) getBranchAttrNoCache(name string) getBranchResult {
name = stripSlash(name)
parent, base := path.Split(name) parent, base := path.Split(name)
parent = stripSlash(parent)
parentBranch := 0 parentBranch := 0
if base != "" { if base != "" {
parentBranch = me.getBranch(parent) parentBranch = me.getBranch(parent)
} }
for i, fs := range me.fileSystems { for i, fs := range me.fileSystems {
if i < parentBranch { if i < parentBranch {
continue continue
...@@ -158,6 +166,7 @@ func (me *UnionFs) getBranchAttrNoCache(name string) getBranchResult { ...@@ -158,6 +166,7 @@ func (me *UnionFs) getBranchAttrNoCache(name string) getBranchResult {
// Make all directories appear writable // Make all directories appear writable
a.Mode |= 0200 a.Mode |= 0200
} }
return getBranchResult{ return getBranchResult{
attr: a, attr: a,
code: s, code: s,
...@@ -231,10 +240,6 @@ func CopyFile(dstName, srcName string) (written int64, err os.Error) { ...@@ -231,10 +240,6 @@ func CopyFile(dstName, srcName string) (written int64, err os.Error) {
return 0, os.NewError("Destination is not a directory.") return 0, os.NewError("Destination is not a directory.")
} }
if err != nil {
// TODO - umask support.
err = os.MkdirAll(dir, 0755)
}
if err != nil { if err != nil {
return 0, err return 0, err
} }
...@@ -248,10 +253,18 @@ func CopyFile(dstName, srcName string) (written int64, err os.Error) { ...@@ -248,10 +253,18 @@ func CopyFile(dstName, srcName string) (written int64, err os.Error) {
return io.Copy(dst, src) return io.Copy(dst, src)
} }
func (me *UnionFs) Promote(name string, src *fuse.LoopbackFileSystem) fuse.Status { func (me *UnionFs) Promote(name string, srcResult getBranchResult) fuse.Status {
writable := me.branches[0] writable := me.branches[0]
_, err := CopyFile(writable.GetPath(name), src.GetPath(name)) sourceFs := me.branches[srcResult.branch]
me.branchCache.Set(name, getBranchResult{nil, fuse.OK, 0})
// Promote directories.
me.promoteDirsTo(name)
_, err := CopyFile(writable.GetPath(name), sourceFs.GetPath(name))
r := me.getBranchResult(name)
r.branch = 0
me.branchCache.Set(name, r)
if err != nil { if err != nil {
log.Println("promote error: ", name, err.String()) log.Println("promote error: ", name, err.String())
return fuse.EPERM return fuse.EPERM
...@@ -264,7 +277,7 @@ func (me *UnionFs) Promote(name string, src *fuse.LoopbackFileSystem) fuse.Statu ...@@ -264,7 +277,7 @@ func (me *UnionFs) Promote(name string, src *fuse.LoopbackFileSystem) fuse.Statu
// Below: implement interface for a FileSystem. // Below: implement interface for a FileSystem.
func (me *UnionFs) Rmdir(path string) (code fuse.Status) { func (me *UnionFs) Rmdir(path string) (code fuse.Status) {
r := me.branchCache.Get(path).(getBranchResult) r := me.getBranchResult(path)
if r.code != fuse.OK { if r.code != fuse.OK {
return r.code return r.code
} }
...@@ -297,7 +310,7 @@ func (me *UnionFs) Rmdir(path string) (code fuse.Status) { ...@@ -297,7 +310,7 @@ func (me *UnionFs) Rmdir(path string) (code fuse.Status) {
} }
func (me *UnionFs) Mkdir(path string, mode uint32) (code fuse.Status) { func (me *UnionFs) Mkdir(path string, mode uint32) (code fuse.Status) {
r := me.branchCache.Get(path).(getBranchResult) r := me.getBranchResult(path)
if r.code != fuse.ENOENT { if r.code != fuse.ENOENT {
return syscall.EEXIST return syscall.EEXIST
} }
...@@ -322,9 +335,9 @@ func (me *UnionFs) Symlink(pointedTo string, linkName string) (code fuse.Status) ...@@ -322,9 +335,9 @@ func (me *UnionFs) Symlink(pointedTo string, linkName string) (code fuse.Status)
} }
func (me *UnionFs) Truncate(path string, offset uint64) (code fuse.Status) { func (me *UnionFs) Truncate(path string, offset uint64) (code fuse.Status) {
branch := me.getBranch(path) r := me.getBranchResult(path)
if branch > 0 { if r.branch > 0 {
code := me.Promote(path, me.branches[branch]) code := me.Promote(path, r)
if code != fuse.OK { if code != fuse.OK {
return code return code
} }
...@@ -334,11 +347,14 @@ func (me *UnionFs) Truncate(path string, offset uint64) (code fuse.Status) { ...@@ -334,11 +347,14 @@ func (me *UnionFs) Truncate(path string, offset uint64) (code fuse.Status) {
} }
func (me *UnionFs) Chmod(name string, mode uint32) (code fuse.Status) { func (me *UnionFs) Chmod(name string, mode uint32) (code fuse.Status) {
r := me.branchCache.Get(name).(getBranchResult) name = stripSlash(name)
if r.attr == nil || r.code != fuse.OK { r := me.getBranchResult(name)
if r.attr == nil {
return r.code
}
if r.code != fuse.OK {
return r.code return r.code
} }
if r.attr.Mode&fuse.S_IFREG == 0 { if r.attr.Mode&fuse.S_IFREG == 0 {
return fuse.EPERM return fuse.EPERM
} }
...@@ -348,13 +364,16 @@ func (me *UnionFs) Chmod(name string, mode uint32) (code fuse.Status) { ...@@ -348,13 +364,16 @@ func (me *UnionFs) Chmod(name string, mode uint32) (code fuse.Status) {
if oldMode != mode { if oldMode != mode {
if r.branch > 0 { if r.branch > 0 {
code := me.Promote(name, me.branches[r.branch]) code := me.Promote(name, r)
if code != fuse.OK { if code != fuse.OK {
return code return code
} }
r.branch = 0
} }
me.fileSystems[0].Chmod(name, mode) me.fileSystems[0].Chmod(name, mode)
} }
r.attr.Mode = (r.attr.Mode &^ 07777) | mode
me.branchCache.Set(name, r)
return fuse.OK return fuse.OK
} }
...@@ -398,14 +417,47 @@ func IsDir(fs fuse.FileSystem, name string) bool { ...@@ -398,14 +417,47 @@ func IsDir(fs fuse.FileSystem, name string) bool {
return code == fuse.OK && a.Mode&fuse.S_IFDIR != 0 return code == fuse.OK && a.Mode&fuse.S_IFDIR != 0
} }
func (me *UnionFs) makeDirTo(name string) fuse.Status { func stripSlash(fn string) string {
dir, base := filepath.Split(name) return strings.TrimRight(fn, string(filepath.Separator))
if base != "" && !IsDir(me.fileSystems[0], dir) { }
err := os.MkdirAll(me.branches[0].GetPath(dir), 0755)
if err != nil { func (me *UnionFs) promoteDirsTo(filename string) fuse.Status {
log.Println("Error creating dir leading to path", name, err) dirName, _ := filepath.Split(filename)
dirName = stripSlash(dirName)
var todo []string
var results []getBranchResult
for dirName != "" {
r := me.getBranchResult(dirName)
if r.code != fuse.OK {
log.Println("path component does not exist", filename, dirName)
}
if r.attr.Mode & fuse.S_IFDIR == 0 {
log.Println("path component is not a directory.", dirName, r)
return fuse.EPERM
}
if r.branch == 0 {
break
}
todo = append(todo, dirName)
results = append(results, r)
dirName, _ = filepath.Split(dirName)
dirName = stripSlash(dirName)
}
for i, _ := range todo {
j := len(todo)-i-1
d := todo[j]
log.Println("Promoting directory", d)
code := me.fileSystems[0].Mkdir(d, 0755)
if code != fuse.OK {
log.Println("Error creating dir leading to path", d, code)
return fuse.EPERM return fuse.EPERM
} }
r := results[j]
r.branch = 0
me.branchCache.Set(d, r)
} }
return fuse.OK return fuse.OK
} }
...@@ -413,14 +465,18 @@ func (me *UnionFs) makeDirTo(name string) fuse.Status { ...@@ -413,14 +465,18 @@ func (me *UnionFs) makeDirTo(name string) fuse.Status {
func (me *UnionFs) Create(name string, flags uint32, mode uint32) (fuseFile fuse.File, code fuse.Status) { func (me *UnionFs) Create(name string, flags uint32, mode uint32) (fuseFile fuse.File, code fuse.Status) {
writable := me.fileSystems[0] writable := me.fileSystems[0]
code = me.makeDirTo(name) code = me.promoteDirsTo(name)
if code != fuse.OK { if code != fuse.OK {
return nil, code return nil, code
} }
fuseFile, code = writable.Create(name, flags, mode) fuseFile, code = writable.Create(name, flags, mode)
if code == fuse.OK { if code == fuse.OK {
me.removeDeletion(name) me.removeDeletion(name)
me.branchCache.Set(name, getBranchResult{nil, fuse.OK, 0})
a := fuse.Attr{
Mode: fuse.S_IFREG | mode,
}
me.branchCache.Set(name, getBranchResult{&a, fuse.OK, 0})
} }
return fuseFile, code return fuseFile, code
} }
...@@ -441,7 +497,7 @@ func (me *UnionFs) GetAttr(name string) (a *fuse.Attr, s fuse.Status) { ...@@ -441,7 +497,7 @@ func (me *UnionFs) GetAttr(name string) (a *fuse.Attr, s fuse.Status) {
if me.isDeleted(name) { if me.isDeleted(name) {
return nil, fuse.ENOENT return nil, fuse.ENOENT
} }
r := me.branchCache.Get(name).(getBranchResult) r := me.getBranchResult(name)
if r.branch < 0 { if r.branch < 0 {
return nil, fuse.ENOENT return nil, fuse.ENOENT
} }
...@@ -547,13 +603,13 @@ func (me *UnionFs) OpenDir(directory string) (stream chan fuse.DirEntry, status ...@@ -547,13 +603,13 @@ func (me *UnionFs) OpenDir(directory string) (stream chan fuse.DirEntry, status
} }
func (me *UnionFs) Rename(src string, dst string) (status fuse.Status) { func (me *UnionFs) Rename(src string, dst string) (status fuse.Status) {
srcResult := me.branchCache.Get(src).(getBranchResult) srcResult := me.getBranchResult(src)
if srcResult.code != fuse.OK { if srcResult.code != fuse.OK {
return srcResult.code return srcResult.code
} }
if srcResult.branch > 0 { if srcResult.branch > 0 {
code := me.Promote(src, me.branches[srcResult.branch]) code := me.Promote(src, srcResult)
if code != fuse.OK { if code != fuse.OK {
return code return code
} }
...@@ -580,9 +636,10 @@ func (me *UnionFs) Rename(src string, dst string) (status fuse.Status) { ...@@ -580,9 +636,10 @@ func (me *UnionFs) Rename(src string, dst string) (status fuse.Status) {
} }
func (me *UnionFs) Open(name string, flags uint32) (fuseFile fuse.File, status fuse.Status) { func (me *UnionFs) Open(name string, flags uint32) (fuseFile fuse.File, status fuse.Status) {
branch := me.getBranch(name) r := me.getBranchResult(name)
if flags&fuse.O_ANYWRITE != 0 && branch > 0 { branch := r.branch
code := me.Promote(name, me.branches[branch]) if flags&fuse.O_ANYWRITE != 0 && r.branch > 0 {
code := me.Promote(name, r)
if code != fuse.OK { if code != fuse.OK {
return nil, code return nil, code
} }
...@@ -591,6 +648,12 @@ func (me *UnionFs) Open(name string, flags uint32) (fuseFile fuse.File, status f ...@@ -591,6 +648,12 @@ func (me *UnionFs) Open(name string, flags uint32) (fuseFile fuse.File, status f
return me.fileSystems[branch].Open(name, uint32(flags)) return me.fileSystems[branch].Open(name, uint32(flags))
} }
func (me *UnionFs) Release(name string) {
me.branchCache.DropEntry(name)
// Refresh to pick up the new size.
me.getBranchResult(name)
}
func (me *UnionFs) Roots() (result []string) { func (me *UnionFs) Roots() (result []string) {
for _, loopback := range me.branches { for _, loopback := range me.branches {
result = append(result, loopback.GetPath("")) result = append(result, loopback.GetPath(""))
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"log" "log"
"testing" "testing"
"time"
) )
var _ = fmt.Print var _ = fmt.Print
...@@ -18,10 +19,12 @@ func TestFilePathHash(t *testing.T) { ...@@ -18,10 +19,12 @@ func TestFilePathHash(t *testing.T) {
t.Log(filePathHash("xyz/abc")) t.Log(filePathHash("xyz/abc"))
} }
const entryTtl = 1
var testOpts = UnionFsOptions{ var testOpts = UnionFsOptions{
DeletionCacheTTLSecs: 0.01, DeletionCacheTTLSecs: entryTtl,
DeletionDirName: "DELETIONS", DeletionDirName: "DELETIONS",
BranchCacheTTLSecs: 0.01, BranchCacheTTLSecs: entryTtl,
} }
func setup(t *testing.T) (workdir string, state *fuse.MountState) { func setup(t *testing.T) (workdir string, state *fuse.MountState) {
...@@ -40,7 +43,13 @@ func setup(t *testing.T) (workdir string, state *fuse.MountState) { ...@@ -40,7 +43,13 @@ func setup(t *testing.T) (workdir string, state *fuse.MountState) {
roots = append(roots, wd+"/ro") roots = append(roots, wd+"/ro")
ufs := NewUnionFs(roots, testOpts) ufs := NewUnionFs(roots, testOpts)
connector := fuse.NewFileSystemConnector(ufs, nil) opts := &fuse.MountOptions{
EntryTimeout: entryTtl,
AttrTimeout: entryTtl,
NegativeTimeout: entryTtl,
}
connector := fuse.NewFileSystemConnector(ufs, opts)
state = fuse.NewMountState(connector) state = fuse.NewMountState(connector)
state.Mount(wd + "/mount") state.Mount(wd + "/mount")
state.Debug = true state.Debug = true
...@@ -56,6 +65,7 @@ func writeToFile(path string, contents string) { ...@@ -56,6 +65,7 @@ func writeToFile(path string, contents string) {
func readFromFile(path string) string { func readFromFile(path string) string {
b, err := ioutil.ReadFile(path) b, err := ioutil.ReadFile(path)
fmt.Println(b)
CheckSuccess(err) CheckSuccess(err)
return string(b) return string(b)
} }
...@@ -136,7 +146,7 @@ func TestChmod(t *testing.T) { ...@@ -136,7 +146,7 @@ func TestChmod(t *testing.T) {
fi, err := os.Lstat(m_fn) fi, err := os.Lstat(m_fn)
CheckSuccess(err) CheckSuccess(err)
if fi.Mode&07777 != 07070 { if fi.Mode&07777 != 07070 {
t.Errorf("Unexpected mode found: %v", fi.Mode) t.Errorf("Unexpected mode found: %o", fi.Mode)
} }
_, err = os.Lstat(wd + "/rw/file") _, err = os.Lstat(wd + "/rw/file")
if err != nil { if err != nil {
...@@ -158,15 +168,17 @@ func TestBasic(t *testing.T) { ...@@ -158,15 +168,17 @@ func TestBasic(t *testing.T) {
} }
checkMapEq(t, names, expected) checkMapEq(t, names, expected)
log.Println("new contents")
writeToFile(wd+"/mount/new", "new contents") writeToFile(wd+"/mount/new", "new contents")
if !fileExists(wd + "/rw/new") { if !fileExists(wd + "/rw/new") {
t.Errorf("missing file in rw layer", names) t.Errorf("missing file in rw layer", names)
} }
if readFromFile(wd+"/mount/new") != "new contents" { contents := readFromFile(wd+"/mount/new")
t.Errorf("read mismatch.") if contents != "new contents" {
t.Errorf("read mismatch: '%v'", contents)
} }
return
writeToFile(wd+"/mount/ro1", "promote me") writeToFile(wd+"/mount/ro1", "promote me")
if !fileExists(wd + "/rw/ro1") { if !fileExists(wd + "/rw/ro1") {
t.Errorf("missing file in rw layer", names) t.Errorf("missing file in rw layer", names)
...@@ -339,3 +351,29 @@ func TestTruncate(t *testing.T) { ...@@ -339,3 +351,29 @@ func TestTruncate(t *testing.T) {
t.Errorf("unexpected rw content %v", content2) t.Errorf("unexpected rw content %v", content2)
} }
} }
func TestCopyChmod(t *testing.T) {
t.Log("TestCopyChmod")
wd, state := setup(t)
defer state.Unmount()
contents := "hello"
fn := wd + "/mount/y"
err := ioutil.WriteFile(fn, []byte(contents), 0644)
CheckSuccess(err)
err = os.Chmod(fn, 0755)
CheckSuccess(err)
fi, err := os.Lstat(fn)
CheckSuccess(err)
if fi.Mode & 0111 == 0 {
t.Errorf("1st attr error %o", fi.Mode)
}
time.Sleep(entryTtl * 1.1e9)
fi, err = os.Lstat(fn)
CheckSuccess(err)
if fi.Mode & 0111 == 0 {
t.Errorf("uncached attr error %o", fi.Mode)
}
}
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