Commit 5ae334dd authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Implement in-memory UnionFs.

parent 437ab542
......@@ -55,7 +55,6 @@ type memNode struct {
fs *MemNodeFs
id int
regular string
link string
info os.FileInfo
}
......@@ -173,23 +172,27 @@ func (me *memNode) Truncate(file File, size uint64, context *Context) (code Stat
me.info.Size = int64(size)
err := os.Truncate(me.filename(), int64(size))
me.info.Ctime_ns = time.Nanoseconds()
return OsErrorToErrno(err)
}
func (me *memNode) Utimens(file File, atime uint64, mtime uint64, context *Context) (code Status) {
me.info.Atime_ns = int64(atime)
me.info.Mtime_ns = int64(mtime)
me.info.Ctime_ns = time.Nanoseconds()
return OK
}
func (me *memNode) Chmod(file File, perms uint32, context *Context) (code Status) {
me.info.Mode = (me.info.Mode ^ 07777) | perms
me.info.Ctime_ns = time.Nanoseconds()
return OK
}
func (me *memNode) Chown(file File, uid uint32, gid uint32, context *Context) (code Status) {
me.info.Uid = int(uid)
me.info.Gid = int(gid)
me.info.Ctime_ns = time.Nanoseconds()
return OK
}
......
......@@ -7,7 +7,8 @@ GOFILES=unionfs.go \
timedcache.go \
cachingfs.go \
autounion.go \
create.go
create.go \
memunionfs.go
DEPS=../fuse
......
package unionfs
import (
"fmt"
"github.com/hanwen/go-fuse/fuse"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
var _ = log.Println
// A unionfs that only uses on-disk backing store for file contents.
type MemUnionFs struct {
fuse.DefaultNodeFileSystem
backingStore string
root *memNode
mutex sync.Mutex
nextFree int
readonly fuse.FileSystem
}
type memNode struct {
fuse.DefaultFsNode
fs *MemUnionFs
original string
// protects mutable data below.
mutex sync.RWMutex
backing string
changed bool
link string
info os.FileInfo
deleted map[string]bool
}
func (me *MemUnionFs) getFilename() string {
me.mutex.Lock()
defer me.mutex.Unlock()
id := me.nextFree
me.nextFree++
return fmt.Sprintf("%s/%d", me.backingStore, id)
}
func (me *MemUnionFs) Root() fuse.FsNode {
return me.root
}
func (me *MemUnionFs) StatFs() *fuse.StatfsOut {
backingFs := &fuse.LoopbackFileSystem{Root: me.backingStore}
return backingFs.StatFs()
}
func (me *MemUnionFs) newNode(isdir bool) *memNode {
n := &memNode{
fs: me,
}
if isdir {
n.deleted = map[string]bool{}
}
now := time.Nanoseconds()
n.info.Mtime_ns = now
n.info.Atime_ns = now
n.info.Ctime_ns = now
return n
}
func NewMemUnionFs(backingStore string, roFs fuse.FileSystem) *MemUnionFs {
me := &MemUnionFs{}
me.backingStore = backingStore
me.readonly = roFs
me.root = me.newNode(true)
return me
}
func (me *memNode) Deletable() bool {
return !me.changed
}
func (me *memNode) touch() {
me.changed = true
me.info.Mtime_ns = time.Nanoseconds()
}
func (me *memNode) ctouch() {
me.changed = true
me.info.Ctime_ns = time.Nanoseconds()
}
func (me *memNode) newNode(isdir bool) *memNode {
n := me.fs.newNode(isdir)
me.Inode().New(isdir, n)
return n
}
func (me *memNode) Readlink(c *fuse.Context) ([]byte, fuse.Status) {
me.mutex.RLock()
defer me.mutex.RUnlock()
return []byte(me.link), fuse.OK
}
func (me *memNode) Lookup(name string, context *fuse.Context) (fi *os.FileInfo, node fuse.FsNode, code fuse.Status) {
me.mutex.RLock()
defer me.mutex.RUnlock()
if _, del := me.deleted[name]; del {
return nil, nil, fuse.ENOENT
}
if me.original == "" && me != me.fs.root {
return nil, nil, fuse.ENOENT
}
fn := filepath.Join(me.original, name)
fi, code = me.fs.readonly.GetAttr(fn, context)
if !code.Ok() {
return nil, nil, code
}
child := me.newNode(fi.Mode & fuse.S_IFDIR != 0)
child.info = *fi
child.original = fn
if child.info.Mode & fuse.S_IFLNK != 0 {
child.link, _ = me.fs.readonly.Readlink(fn, context)
}
me.Inode().AddChild(name, child.Inode())
return fi, child, fuse.OK
}
func (me *memNode) Mkdir(name string, mode uint32, context *fuse.Context) (fi *os.FileInfo, newNode fuse.FsNode, code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
me.deleted[name] = false, false
n := me.newNode(true)
n.changed = true
n.info.Mode = mode | fuse.S_IFDIR
me.Inode().AddChild(name, n.Inode())
me.touch()
return &n.info, n, fuse.OK
}
func (me *memNode) Unlink(name string, context *fuse.Context) (code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
me.deleted[name] = true
ch := me.Inode().RmChild(name)
if ch == nil {
return fuse.ENOENT
}
me.touch()
return fuse.OK
}
func (me *memNode) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
return me.Unlink(name, context)
}
func (me *memNode) Symlink(name string, content string, context *fuse.Context) (fi *os.FileInfo, newNode fuse.FsNode, code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
n := me.newNode(false)
n.info.Mode = fuse.S_IFLNK | 0777
n.link = content
me.Inode().AddChild(name, n.Inode())
me.touch()
me.deleted[name] = false, false
return &n.info, n, fuse.OK
}
func (me *memNode) Rename(oldName string, newParent fuse.FsNode, newName string, context *fuse.Context) (code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
ch := me.Inode().RmChild(oldName)
me.deleted[oldName] = true
newParent.Inode().RmChild(newName)
newParent.Inode().AddChild(newName, ch)
me.deleted[newName] = false, false
me.touch()
return fuse.OK
}
func (me *memNode) Link(name string, existing fuse.FsNode, context *fuse.Context) (fi *os.FileInfo, newNode fuse.FsNode, code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
me.Inode().AddChild(name, existing.Inode())
fi, code = existing.GetAttr(nil, context)
me.touch()
me.deleted[name] = false, false
return fi, existing, code
}
func (me *memNode) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file fuse.File, fi *os.FileInfo, newNode fuse.FsNode, code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
n := me.newNode(false)
n.info.Mode = mode | fuse.S_IFREG
n.changed = true
n.backing = me.fs.getFilename()
f, err := os.Create(n.backing)
if err != nil {
return nil, nil, nil, fuse.OsErrorToErrno(err)
}
me.Inode().AddChild(name, n.Inode())
me.touch()
me.deleted[name] = false, false
return n.newFile(&fuse.LoopbackFile{File: f}, true), &n.info, n, fuse.OK
}
type memNodeFile struct {
fuse.File
writable bool
node *memNode
}
func (me *memNodeFile) InnerFile() fuse.File {
return me.File
}
func (me *memNodeFile) Flush() fuse.Status {
code := me.File.Flush()
if me.writable {
me.node.mutex.Lock()
defer me.node.mutex.Unlock()
fi, _ := me.File.GetAttr()
me.node.info.Size = fi.Size
me.node.info.Blocks = fi.Blocks
}
return code
}
func (me *memNode) newFile(f fuse.File, writable bool) fuse.File {
return &memNodeFile{
File: f,
writable: writable,
node: me,
}
}
// Must run inside mutex.
func (me *memNode) promote() {
if me.backing == "" {
me.backing = me.fs.getFilename()
destfs := &fuse.LoopbackFileSystem{Root: "/"}
fuse.CopyFile(me.fs.readonly, destfs,
me.original, strings.TrimLeft(me.backing, "/"), nil)
files := me.Inode().Files(0)
for _, f := range files {
mf := f.File.(*memNodeFile)
inner := mf.File
osFile, err := os.Open(me.backing)
if err != nil {
panic("error opening backing file")
}
mf.File = &fuse.LoopbackFile{File: osFile}
inner.Flush()
inner.Release()
}
}
}
func (me *memNode) Open(flags uint32, context *fuse.Context) (file fuse.File, code fuse.Status) {
if flags & fuse.O_ANYWRITE != 0 {
me.mutex.Lock()
defer me.mutex.Unlock()
me.promote()
me.touch()
}
if me.backing != "" {
f, err := os.OpenFile(me.backing, int(flags), 0666)
if err != nil {
return nil, fuse.OsErrorToErrno(err)
}
wr := flags & fuse.O_ANYWRITE != 0
return me.newFile(&fuse.LoopbackFile{File: f}, wr), fuse.OK
}
file, code = me.fs.readonly.Open(me.original, flags, context)
if !code.Ok() {
return nil, code
}
return me.newFile(file, false), fuse.OK
}
func (me *memNode) GetAttr(file fuse.File, context *fuse.Context) (fi *os.FileInfo, code fuse.Status) {
me.mutex.RLock()
defer me.mutex.RUnlock()
info := me.info
if file != nil {
fi, _ := file.GetAttr()
info.Size = fi.Size
}
return &info, fuse.OK
}
func (me *memNode) Truncate(file fuse.File, size uint64, context *fuse.Context) (code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
me.promote()
if file != nil {
return file.Truncate(size)
}
me.info.Size = int64(size)
err := os.Truncate(me.backing, int64(size))
me.touch()
return fuse.OsErrorToErrno(err)
}
func (me *memNode) Utimens(file fuse.File, atime uint64, mtime uint64, context *fuse.Context) (code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
me.info.Atime_ns = int64(atime)
me.info.Mtime_ns = int64(mtime)
me.ctouch()
return fuse.OK
}
func (me *memNode) Chmod(file fuse.File, perms uint32, context *fuse.Context) (code fuse.Status) {
me.mutex.Lock()
defer me.mutex.Unlock()
me.info.Mode = (me.info.Mode &^ 07777) | perms
me.ctouch()
return fuse.OK
}
func (me *memNode) Chown(file fuse.File, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
if context.Uid != 0 {
return fuse.EPERM
}
me.mutex.Lock()
defer me.mutex.Unlock()
me.info.Uid = int(uid)
me.info.Gid = int(gid)
me.ctouch()
return fuse.OK
}
func (me *memNode) OpenDir(context *fuse.Context) (stream chan fuse.DirEntry, code fuse.Status) {
me.mutex.RLock()
defer me.mutex.RUnlock()
ch := map[string]uint32{}
if me.original != "" || me == me.fs.root {
stream, code = me.fs.readonly.OpenDir(me.original, context)
for e := range stream {
ch[e.Name] = e.Mode
}
}
for k, n := range me.Inode().FsChildren() {
fi, code := n.FsNode().GetAttr(nil, nil)
if !code.Ok() {
panic("child does not have mode.")
}
ch[k] = fi.Mode
}
for k, _ := range me.deleted {
ch[k] = 0, false
}
stream = make(chan fuse.DirEntry, len(ch))
for k, v := range ch {
stream <- fuse.DirEntry{Name: k, Mode: v}
}
close(stream)
return stream, fuse.OK
}
package unionfs
import (
"exec"
"os"
"github.com/hanwen/go-fuse/fuse"
"io/ioutil"
"fmt"
"log"
"path/filepath"
"syscall"
"testing"
"time"
)
var _ = fmt.Print
var _ = log.Print
var CheckSuccess = fuse.CheckSuccess
func setupMemUfs(t *testing.T) (workdir string, cleanup func()) {
// Make sure system setting does not affect test.
syscall.Umask(0)
wd, _ := ioutil.TempDir("", "")
err := os.Mkdir(wd+"/mount", 0700)
fuse.CheckSuccess(err)
err = os.Mkdir(wd+"/backing", 0700)
fuse.CheckSuccess(err)
os.Mkdir(wd+"/ro", 0700)
fuse.CheckSuccess(err)
roFs := NewCachingFileSystem(fuse.NewLoopbackFileSystem(wd+"/ro"), 0.0)
memFs := NewMemUnionFs(wd+"/backing", roFs)
// We configure timeouts are smaller, so we can check for
// UnionFs's cache consistency.
opts := &fuse.FileSystemOptions{
EntryTimeout: .5 * entryTtl,
AttrTimeout: .5 * entryTtl,
NegativeTimeout: .5 * entryTtl,
}
state, conn, err := fuse.MountNodeFileSystem(wd+"/mount", memFs, opts)
CheckSuccess(err)
conn.Debug = fuse.VerboseTest()
state.Debug = fuse.VerboseTest()
go state.Loop()
return wd, func() {
state.Unmount()
os.RemoveAll(wd)
}
}
func TestMemUnionFsSymlink(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := os.Symlink("/foobar", wd+"/mount/link")
CheckSuccess(err)
val, err := os.Readlink(wd + "/mount/link")
CheckSuccess(err)
if val != "/foobar" {
t.Errorf("symlink mismatch: %v", val)
}
}
func TestMemUnionFsSymlinkPromote(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := os.Mkdir(wd+"/ro/subdir", 0755)
CheckSuccess(err)
err = os.Symlink("/foobar", wd+"/mount/subdir/link")
CheckSuccess(err)
}
func TestMemUnionFsChtimes(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
writeToFile(wd+"/ro/file", "a")
err := os.Chtimes(wd+"/ro/file", 42e9, 43e9)
CheckSuccess(err)
err = os.Chtimes(wd+"/mount/file", 82e9, 83e9)
CheckSuccess(err)
fi, err := os.Lstat(wd + "/mount/file")
if fi.Atime_ns != 82e9 || fi.Mtime_ns != 83e9 {
t.Error("Incorrect timestamp", fi)
}
}
func TestMemUnionFsChmod(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
ro_fn := wd + "/ro/file"
m_fn := wd + "/mount/file"
writeToFile(ro_fn, "a")
err := os.Chmod(m_fn, 07070)
CheckSuccess(err)
fi, err := os.Lstat(m_fn)
CheckSuccess(err)
if fi.Mode&07777 != 07070 {
t.Errorf("Unexpected mode found: %o", fi.Mode)
}
}
func TestMemUnionFsChown(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
ro_fn := wd + "/ro/file"
m_fn := wd + "/mount/file"
writeToFile(ro_fn, "a")
err := os.Chown(m_fn, 0, 0)
code := fuse.OsErrorToErrno(err)
if code != fuse.EPERM {
t.Error("Unexpected error code", code, err)
}
}
func TestMemUnionFsDelete(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
writeToFile(wd+"/ro/file", "a")
_, err := os.Lstat(wd + "/mount/file")
CheckSuccess(err)
err = os.Remove(wd + "/mount/file")
CheckSuccess(err)
_, err = os.Lstat(wd + "/mount/file")
if err == nil {
t.Fatal("should have disappeared.")
}
}
func TestMemUnionFsBasic(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
writeToFile(wd+"/mount/rw", "a")
writeToFile(wd+"/ro/ro1", "a")
writeToFile(wd+"/ro/ro2", "b")
names := dirNames(wd + "/mount")
expected := map[string]bool{
"rw": true, "ro1": true, "ro2": true,
}
checkMapEq(t, names, expected)
writeToFile(wd+"/mount/new", "new contents")
contents := readFromFile(wd + "/mount/new")
if contents != "new contents" {
t.Errorf("read mismatch: '%v'", contents)
}
writeToFile(wd+"/mount/ro1", "promote me")
remove(wd + "/mount/new")
names = dirNames(wd + "/mount")
checkMapEq(t, names, map[string]bool{
"rw": true, "ro1": true, "ro2": true,
})
remove(wd + "/mount/ro1")
names = dirNames(wd + "/mount")
checkMapEq(t, names, map[string]bool{
"rw": true, "ro2": true,
})
}
func TestMemUnionFsPromote(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := os.Mkdir(wd+"/ro/subdir", 0755)
CheckSuccess(err)
writeToFile(wd+"/ro/subdir/file", "content")
writeToFile(wd+"/mount/subdir/file", "other-content")
}
func TestMemUnionFsCreate(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := os.MkdirAll(wd+"/ro/subdir/sub2", 0755)
CheckSuccess(err)
writeToFile(wd+"/mount/subdir/sub2/file", "other-content")
_, err = os.Lstat(wd + "/mount/subdir/sub2/file")
CheckSuccess(err)
}
func TestMemUnionFsOpenUndeletes(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
writeToFile(wd+"/ro/file", "X")
err := os.Remove(wd + "/mount/file")
CheckSuccess(err)
writeToFile(wd+"/mount/file", "X")
_, err = os.Lstat(wd + "/mount/file")
CheckSuccess(err)
}
func TestMemUnionFsMkdir(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
dirname := wd + "/mount/subdir"
err := os.Mkdir(dirname, 0755)
CheckSuccess(err)
err = os.Remove(dirname)
CheckSuccess(err)
}
func TestMemUnionFsMkdirPromote(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
dirname := wd + "/ro/subdir/subdir2"
err := os.MkdirAll(dirname, 0755)
CheckSuccess(err)
err = os.Mkdir(wd+"/mount/subdir/subdir2/dir3", 0755)
CheckSuccess(err)
}
func TestMemUnionFsRmdirMkdir(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := os.Mkdir(wd+"/ro/subdir", 0755)
CheckSuccess(err)
dirname := wd + "/mount/subdir"
err = os.Remove(dirname)
CheckSuccess(err)
err = os.Mkdir(dirname, 0755)
CheckSuccess(err)
}
func TestMemUnionFsLink(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
content := "blabla"
fn := wd + "/ro/file"
err := ioutil.WriteFile(fn, []byte(content), 0666)
CheckSuccess(err)
err = os.Link(wd+"/mount/file", wd+"/mount/linked")
CheckSuccess(err)
fi2, err := os.Lstat(wd + "/mount/linked")
CheckSuccess(err)
fi1, err := os.Lstat(wd + "/mount/file")
CheckSuccess(err)
if fi1.Ino != fi2.Ino {
t.Errorf("inode numbers should be equal for linked files %v, %v", fi1.Ino, fi2.Ino)
}
c, err := ioutil.ReadFile(wd + "/mount/linked")
if string(c) != content {
t.Errorf("content mismatch got %q want %q", string(c), content)
}
}
func TestMemUnionFsTruncate(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
writeToFile(wd+"/ro/file", "hello")
os.Truncate(wd+"/mount/file", 2)
content := readFromFile(wd + "/mount/file")
if content != "he" {
t.Errorf("unexpected content %v", content)
}
}
func TestMemUnionFsCopyChmod(t *testing.T) {
t.Log("TestCopyChmod")
wd, clean := setupMemUfs(t)
defer clean()
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)
}
}
func TestMemUnionFsTruncateTimestamp(t *testing.T) {
t.Log("TestTruncateTimestamp")
wd, clean := setupMemUfs(t)
defer clean()
contents := "hello"
fn := wd + "/mount/y"
err := ioutil.WriteFile(fn, []byte(contents), 0644)
CheckSuccess(err)
time.Sleep(0.2e9)
truncTs := time.Nanoseconds()
err = os.Truncate(fn, 3)
CheckSuccess(err)
fi, err := os.Lstat(fn)
CheckSuccess(err)
if abs(truncTs-fi.Mtime_ns) > 0.1e9 {
t.Error("timestamp drift", truncTs, fi.Mtime_ns)
}
}
func TestMemUnionFsRemoveAll(t *testing.T) {
t.Log("TestRemoveAll")
wd, clean := setupMemUfs(t)
defer clean()
err := os.MkdirAll(wd+"/ro/dir/subdir", 0755)
CheckSuccess(err)
contents := "hello"
fn := wd + "/ro/dir/subdir/y"
err = ioutil.WriteFile(fn, []byte(contents), 0644)
CheckSuccess(err)
err = os.RemoveAll(wd + "/mount/dir")
if err != nil {
t.Error("Should delete all")
}
for _, f := range []string{"dir/subdir/y", "dir/subdir", "dir"} {
if fi, _ := os.Lstat(filepath.Join(wd, "mount", f)); fi != nil {
t.Errorf("file %s should have disappeared: %v", f, fi)
}
}
}
func TestMemUnionFsRmRf(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := os.MkdirAll(wd+"/ro/dir/subdir", 0755)
CheckSuccess(err)
contents := "hello"
fn := wd + "/ro/dir/subdir/y"
err = ioutil.WriteFile(fn, []byte(contents), 0644)
CheckSuccess(err)
bin, err := exec.LookPath("rm")
CheckSuccess(err)
cmd := exec.Command(bin, "-rf", wd+"/mount/dir")
err = cmd.Run()
if err != nil {
t.Fatal("rm -rf returned error:", err)
}
for _, f := range []string{"dir/subdir/y", "dir/subdir", "dir"} {
if fi, _ := os.Lstat(filepath.Join(wd, "mount", f)); fi != nil {
t.Errorf("file %s should have disappeared: %v", f, fi)
}
}
}
func TestMemUnionFsDeletedGetAttr(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := ioutil.WriteFile(wd+"/ro/file", []byte("blabla"), 0644)
CheckSuccess(err)
f, err := os.Open(wd + "/mount/file")
CheckSuccess(err)
defer f.Close()
err = os.Remove(wd + "/mount/file")
CheckSuccess(err)
if fi, err := f.Stat(); err != nil || !fi.IsRegular() {
t.Fatalf("stat returned error or non-file: %v %v", err, fi)
}
}
func TestMemUnionFsDoubleOpen(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := ioutil.WriteFile(wd+"/ro/file", []byte("blablabla"), 0644)
CheckSuccess(err)
roFile, err := os.Open(wd + "/mount/file")
CheckSuccess(err)
defer roFile.Close()
rwFile, err := os.OpenFile(wd+"/mount/file", os.O_WRONLY|os.O_TRUNC, 0666)
CheckSuccess(err)
defer rwFile.Close()
output, err := ioutil.ReadAll(roFile)
CheckSuccess(err)
if len(output) != 0 {
t.Errorf("After r/w truncation, r/o file should be empty too: %q", string(output))
}
want := "hello"
_, err = rwFile.Write([]byte(want))
CheckSuccess(err)
b := make([]byte, 100)
roFile.Seek(0, 0)
n, err := roFile.Read(b)
CheckSuccess(err)
b = b[:n]
if string(b) != "hello" {
t.Errorf("r/w and r/o file are not synchronized: got %q want %q", string(b), want)
}
}
func TestMemUnionFsFdLeak(t *testing.T) {
beforeEntries, err := ioutil.ReadDir("/proc/self/fd")
CheckSuccess(err)
wd, clean := setupMemUfs(t)
err = ioutil.WriteFile(wd+"/ro/file", []byte("blablabla"), 0644)
CheckSuccess(err)
contents, err := ioutil.ReadFile(wd + "/mount/file")
CheckSuccess(err)
err = ioutil.WriteFile(wd+"/mount/file", contents, 0644)
CheckSuccess(err)
clean()
afterEntries, err := ioutil.ReadDir("/proc/self/fd")
CheckSuccess(err)
if len(afterEntries) != len(beforeEntries) {
t.Errorf("/proc/self/fd changed size: after %v before %v", len(beforeEntries), len(afterEntries))
}
}
func TestMemUnionFsStatFs(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
s1 := syscall.Statfs_t{}
err := syscall.Statfs(wd+"/mount", &s1)
if err != 0 {
t.Fatal("statfs mnt", err)
}
if s1.Bsize == 0 {
t.Fatal("Expect blocksize > 0")
}
}
func TestMemUnionFsFlushSize(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
fn := wd + "/mount/file"
f, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE, 0644)
CheckSuccess(err)
fi, err := f.Stat()
CheckSuccess(err)
n, err := f.Write([]byte("hello"))
CheckSuccess(err)
f.Close()
fi, err = os.Lstat(fn)
CheckSuccess(err)
if fi.Size != int64(n) {
t.Errorf("got %d from Stat().Size, want %d", fi.Size, n)
}
}
func TestMemUnionFsFlushRename(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
err := ioutil.WriteFile(wd+"/mount/file", []byte("x"), 0644)
fn := wd + "/mount/tmp"
f, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE, 0644)
CheckSuccess(err)
fi, err := f.Stat()
CheckSuccess(err)
n, err := f.Write([]byte("hello"))
CheckSuccess(err)
f.Close()
dst := wd + "/mount/file"
err = os.Rename(fn, dst)
CheckSuccess(err)
fi, err = os.Lstat(dst)
CheckSuccess(err)
if fi.Size != int64(n) {
t.Errorf("got %d from Stat().Size, want %d", fi.Size, n)
}
}
func TestMemUnionFsTruncGetAttr(t *testing.T) {
wd, clean := setupMemUfs(t)
defer clean()
c := []byte("hello")
f, err := os.OpenFile(wd+"/mount/file", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
CheckSuccess(err)
_, err = f.Write(c)
CheckSuccess(err)
err = f.Close()
CheckSuccess(err)
fi, err := os.Lstat(wd+"/mount/file")
if fi.Size != int64(len(c)) {
t.Fatalf("Length mismatch got %d want %d", fi.Size, len(c))
}
}
......@@ -16,8 +16,6 @@ import (
var _ = fmt.Print
var _ = log.Print
var CheckSuccess = fuse.CheckSuccess
func TestFilePathHash(t *testing.T) {
// Simple test coverage.
t.Log(filePathHash("xyz/abc"))
......@@ -101,8 +99,7 @@ func dirNames(path string) map[string]bool {
func checkMapEq(t *testing.T, m1, m2 map[string]bool) {
if !mapEq(m1, m2) {
msg := fmt.Sprintf("mismatch: got %v != expect %v", m1, m2)
log.Print(msg)
t.Error(msg)
panic(msg)
}
}
......
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