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

AutoUnionFs: Allow removal of symlinks too.

Add a test for AutoUnionFs.
parent 75f62dbe
...@@ -35,6 +35,7 @@ func main() { ...@@ -35,6 +35,7 @@ func main() {
AttrTimeout: 1.0, AttrTimeout: 1.0,
NegativeTimeout: 1.0, NegativeTimeout: 1.0,
}, },
UpdateOnMount: true,
} }
gofs := unionfs.NewAutoUnionFs(flag.Arg(1), options) gofs := unionfs.NewAutoUnionFs(flag.Arg(1), options)
......
...@@ -5,7 +5,7 @@ package fuse ...@@ -5,7 +5,7 @@ package fuse
PathFilesystemConnector is a lowlevel FUSE filesystem that translates PathFilesystemConnector is a lowlevel FUSE filesystem that translates
from inode numbers (as delivered by the kernel) to traditional path from inode numbers (as delivered by the kernel) to traditional path
names. The paths are then used as arguments for methods of names. The paths are then used as arguments for methods of
PathFilesystem instances. PathFilesystem instances.
PathFilesystemConnector supports mounts of different PathFilesystem PathFilesystemConnector supports mounts of different PathFilesystem
on top of each other's directories. on top of each other's directories.
...@@ -535,5 +535,4 @@ func (me *FileSystemConnector) getOpenFileData(nodeid uint64, fh uint64) (f File ...@@ -535,5 +535,4 @@ func (me *FileSystemConnector) getOpenFileData(nodeid uint64, fh uint64) (f File
} }
return return
} }
...@@ -32,6 +32,9 @@ type AutoUnionFs struct { ...@@ -32,6 +32,9 @@ type AutoUnionFs struct {
type AutoUnionFsOptions struct { type AutoUnionFsOptions struct {
UnionFsOptions UnionFsOptions
fuse.MountOptions fuse.MountOptions
// If set, run updateKnownFses() after mounting.
UpdateOnMount bool
} }
const ( const (
...@@ -52,7 +55,9 @@ func NewAutoUnionFs(directory string, options AutoUnionFsOptions) *AutoUnionFs { ...@@ -52,7 +55,9 @@ func NewAutoUnionFs(directory string, options AutoUnionFsOptions) *AutoUnionFs {
func (me *AutoUnionFs) Mount(connector *fuse.FileSystemConnector) fuse.Status { func (me *AutoUnionFs) Mount(connector *fuse.FileSystemConnector) fuse.Status {
me.connector = connector me.connector = connector
time.AfterFunc(0.1e9, func() { me.updateKnownFses() }) if me.options.UpdateOnMount {
time.AfterFunc(0.1e9, func() { me.updateKnownFses() })
}
return fuse.OK return fuse.OK
} }
...@@ -62,25 +67,61 @@ func (me *AutoUnionFs) addAutomaticFs(roots []string) { ...@@ -62,25 +67,61 @@ func (me *AutoUnionFs) addAutomaticFs(roots []string) {
me.addFs(name, roots) me.addFs(name, roots)
} }
func (me *AutoUnionFs) addFs(name string, roots []string) bool { func (me *AutoUnionFs) createFs(name string, roots []string) (*UnionFs, fuse.Status) {
if name == _CONFIG || name == _STATUS { me.lock.Lock()
log.Println("Illegal name for overlay", roots) defer me.lock.Unlock()
return false
} used := make(map[string]string)
for workspace, v := range me.knownFileSystems {
used[v.Roots()[0]] = workspace
}
workspace, ok := used[roots[0]]
if ok {
log.Printf("Already have a union FS for directory %s in workspace %s",
roots[0], workspace)
return nil, fuse.EBUSY
}
var gofs *UnionFs
if me.knownFileSystems[name] == nil {
log.Println("Adding UnionFs for roots", roots)
gofs = NewUnionFs(roots, me.options.UnionFsOptions)
me.knownFileSystems[name] = gofs
}
return gofs, fuse.OK
}
func (me *AutoUnionFs) rmFs(name string) (code fuse.Status) {
me.lock.Lock() me.lock.Lock()
var gofs *UnionFs defer me.lock.Unlock()
if me.knownFileSystems[name] == nil {
log.Println("Adding UnionFs for roots", roots) fs := me.knownFileSystems[name]
gofs = NewUnionFs(roots, me.options.UnionFsOptions) if fs == nil {
me.knownFileSystems[name] = gofs return fuse.ENOENT
}
code = me.connector.Unmount(name)
if code.Ok() {
me.knownFileSystems[name] = nil, false
} else {
log.Println("Unmount failed for %s. Code %v", name, code)
} }
me.lock.Unlock()
return code
}
func (me *AutoUnionFs) addFs(name string, roots []string) (code fuse.Status) {
if name == _CONFIG || name == _STATUS {
log.Println("Illegal name for overlay", roots)
return fuse.EINVAL
}
gofs, code := me.createFs(name, roots)
if gofs != nil { if gofs != nil {
me.connector.Mount("/"+name, gofs, &me.options.MountOptions) me.connector.Mount("/"+name, gofs, &me.options.MountOptions)
} }
return true return code
} }
// TODO - should hide these methods. // TODO - should hide these methods.
...@@ -152,15 +193,26 @@ func (me *AutoUnionFs) Symlink(pointedTo string, linkName string) (code fuse.Sta ...@@ -152,15 +193,26 @@ func (me *AutoUnionFs) Symlink(pointedTo string, linkName string) (code fuse.Sta
} }
name := comps[1] name := comps[1]
if !me.addFs(name, roots) { return me.addFs(name, roots)
return fuse.EPERM
}
return fuse.OK
} }
return fuse.EPERM return fuse.EPERM
} }
func (me *AutoUnionFs) Unlink(path string) (code fuse.Status) {
comps := strings.Split(path, "/", -1)
if len(comps) != 2 {
return fuse.EPERM
}
if comps[0] == _CONFIG {
code = me.rmFs(comps[1])
} else {
code = fuse.ENOENT
}
return code
}
// Must define this, because ENOSYS will suspend all GetXAttr calls. // Must define this, because ENOSYS will suspend all GetXAttr calls.
func (me *AutoUnionFs) GetXAttr(name string, attr string) ([]byte, fuse.Status) { func (me *AutoUnionFs) GetXAttr(name string, attr string) ([]byte, fuse.Status) {
return nil, syscall.ENODATA return nil, syscall.ENODATA
......
package unionfs
import (
"os"
"github.com/hanwen/go-fuse/fuse"
"io/ioutil"
"fmt"
"log"
"testing"
"time"
)
var _ = fmt.Print
var _ = log.Print
const entryTtl = 0.1
var testAOpts = AutoUnionFsOptions{
UnionFsOptions: testOpts,
MountOptions: fuse.MountOptions{
EntryTimeout: entryTtl,
AttrTimeout: entryTtl,
NegativeTimeout: 0,
},
}
func WriteFile(name string, contents string) {
err := ioutil.WriteFile(name, []byte(contents), 0644)
CheckSuccess(err)
}
func setup(t *testing.T) (workdir string, state *fuse.MountState) {
wd := fuse.MakeTempDir()
err := os.Mkdir(wd+"/mount", 0700)
fuse.CheckSuccess(err)
err = os.Mkdir(wd+"/store", 0700)
fuse.CheckSuccess(err)
os.Mkdir(wd+"/ro", 0700)
fuse.CheckSuccess(err)
WriteFile(wd+"/ro/file1", "file1")
WriteFile(wd+"/ro/file2", "file2")
fs := NewAutoUnionFs(wd+"/store", testAOpts)
connector := fuse.NewFileSystemConnector(fs, &testAOpts.MountOptions)
state = fuse.NewMountState(connector)
state.Mount(wd + "/mount")
state.Debug = true
go state.Loop(false)
return wd, state
}
func TestAutoFsSymlink(t *testing.T) {
wd, state := setup(t)
defer state.Unmount()
err := os.Mkdir(wd+"/store/foo", 0755)
CheckSuccess(err)
os.Symlink(wd+"/ro", wd+"/store/foo/READONLY")
CheckSuccess(err)
err = os.Symlink(wd+"/store/foo", wd+"/mount/config/bar")
CheckSuccess(err)
fi, err := os.Lstat(wd+"/mount/bar/file1")
CheckSuccess(err)
err = os.Remove(wd+"/mount/config/bar")
CheckSuccess(err)
// Need time for the unmount to be noticed.
log.Println("sleeping...")
time.Sleep(entryTtl*2e9)
fi, _ = os.Lstat(wd+"/mount/foo")
if fi != nil {
t.Error("Should not have file:", fi)
}
_, err = ioutil.ReadDir(wd+"/mount/config")
CheckSuccess(err)
_, err = os.Lstat(wd+"/mount/foo/file1")
CheckSuccess(err)
}
func TestCreationChecks(t *testing.T) {
wd, state := setup(t)
defer state.Unmount()
err := os.Mkdir(wd+"/store/foo", 0755)
CheckSuccess(err)
os.Symlink(wd+"/ro", wd+"/store/foo/READONLY")
CheckSuccess(err)
err = os.Mkdir(wd+"/store/ws2", 0755)
CheckSuccess(err)
os.Symlink(wd+"/ro", wd+"/store/ws2/READONLY")
CheckSuccess(err)
err = os.Symlink(wd+"/store/foo", wd+"/mount/config/bar")
CheckSuccess(err)
err = os.Symlink(wd+"/store/foo", wd+"/mount/config/foo")
code := fuse.OsErrorToErrno(err)
if code != fuse.EBUSY {
t.Error("Should return EBUSY", err)
}
err = os.Symlink(wd+"/store/ws2", wd+"/mount/config/config")
code = fuse.OsErrorToErrno(err)
if code != fuse.EINVAL {
t.Error("Should return EINVAL", err)
}
}
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
var _ = fmt.Print var _ = fmt.Print
var _ = log.Print var _ = log.Print
var CheckSuccess = fuse.CheckSuccess var CheckSuccess = fuse.CheckSuccess
func TestFilePathHash(t *testing.T) { func TestFilePathHash(t *testing.T) {
...@@ -19,15 +20,13 @@ func TestFilePathHash(t *testing.T) { ...@@ -19,15 +20,13 @@ 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: entryTtl, DeletionCacheTTLSecs: entryTtl,
DeletionDirName: "DELETIONS", DeletionDirName: "DELETIONS",
BranchCacheTTLSecs: entryTtl, BranchCacheTTLSecs: entryTtl,
} }
func setup(t *testing.T) (workdir string, state *fuse.MountState) { func setupUfs(t *testing.T) (workdir string, state *fuse.MountState) {
wd := fuse.MakeTempDir() wd := fuse.MakeTempDir()
err := os.Mkdir(wd+"/mount", 0700) err := os.Mkdir(wd+"/mount", 0700)
fuse.CheckSuccess(err) fuse.CheckSuccess(err)
...@@ -48,7 +47,7 @@ func setup(t *testing.T) (workdir string, state *fuse.MountState) { ...@@ -48,7 +47,7 @@ func setup(t *testing.T) (workdir string, state *fuse.MountState) {
AttrTimeout: entryTtl, AttrTimeout: entryTtl,
NegativeTimeout: entryTtl, NegativeTimeout: entryTtl,
} }
connector := fuse.NewFileSystemConnector(ufs, opts) connector := fuse.NewFileSystemConnector(ufs, opts)
state = fuse.NewMountState(connector) state = fuse.NewMountState(connector)
state.Mount(wd + "/mount") state.Mount(wd + "/mount")
...@@ -119,7 +118,7 @@ func remove(path string) { ...@@ -119,7 +118,7 @@ func remove(path string) {
} }
func TestSymlink(t *testing.T) { func TestSymlink(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
err := os.Symlink("/foobar", wd+"/mount/link") err := os.Symlink("/foobar", wd+"/mount/link")
...@@ -134,9 +133,9 @@ func TestSymlink(t *testing.T) { ...@@ -134,9 +133,9 @@ func TestSymlink(t *testing.T) {
} }
func TestChtimes(t *testing.T) { func TestChtimes(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
writeToFile(wd+"/ro/file", "a") writeToFile(wd+"/ro/file", "a")
err := os.Chtimes(wd + "/ro/file", 42e9, 43e9) err := os.Chtimes(wd + "/ro/file", 42e9, 43e9)
CheckSuccess(err) CheckSuccess(err)
...@@ -151,7 +150,7 @@ func TestChtimes(t *testing.T) { ...@@ -151,7 +150,7 @@ func TestChtimes(t *testing.T) {
} }
func TestChmod(t *testing.T) { func TestChmod(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
ro_fn := wd + "/ro/file" ro_fn := wd + "/ro/file"
...@@ -165,7 +164,7 @@ func TestChmod(t *testing.T) { ...@@ -165,7 +164,7 @@ func TestChmod(t *testing.T) {
if code != fuse.EPERM { if code != fuse.EPERM {
t.Error("Unexpected error code", code, err) t.Error("Unexpected error code", code, err)
} }
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 {
...@@ -178,7 +177,7 @@ func TestChmod(t *testing.T) { ...@@ -178,7 +177,7 @@ func TestChmod(t *testing.T) {
} }
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
writeToFile(wd+"/rw/rw", "a") writeToFile(wd+"/rw/rw", "a")
...@@ -241,7 +240,7 @@ func TestBasic(t *testing.T) { ...@@ -241,7 +240,7 @@ func TestBasic(t *testing.T) {
} }
func TestPromote(t *testing.T) { func TestPromote(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
err := os.Mkdir(wd+"/ro/subdir", 0755) err := os.Mkdir(wd+"/ro/subdir", 0755)
...@@ -251,7 +250,7 @@ func TestPromote(t *testing.T) { ...@@ -251,7 +250,7 @@ func TestPromote(t *testing.T) {
} }
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
err := os.MkdirAll(wd+"/ro/subdir/sub2", 0755) err := os.MkdirAll(wd+"/ro/subdir/sub2", 0755)
...@@ -262,7 +261,7 @@ func TestCreate(t *testing.T) { ...@@ -262,7 +261,7 @@ func TestCreate(t *testing.T) {
} }
func TestOpenUndeletes(t *testing.T) { func TestOpenUndeletes(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
writeToFile(wd+"/ro/file", "X") writeToFile(wd+"/ro/file", "X")
...@@ -274,7 +273,7 @@ func TestOpenUndeletes(t *testing.T) { ...@@ -274,7 +273,7 @@ func TestOpenUndeletes(t *testing.T) {
} }
func TestMkdir(t *testing.T) { func TestMkdir(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
dirname := wd + "/mount/subdir" dirname := wd + "/mount/subdir"
...@@ -286,7 +285,7 @@ func TestMkdir(t *testing.T) { ...@@ -286,7 +285,7 @@ func TestMkdir(t *testing.T) {
} }
func TestMkdirPromote(t *testing.T) { func TestMkdirPromote(t *testing.T) {
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
dirname := wd + "/ro/subdir/subdir2" dirname := wd + "/ro/subdir/subdir2"
...@@ -321,7 +320,7 @@ func TestRename(t *testing.T) { ...@@ -321,7 +320,7 @@ func TestRename(t *testing.T) {
for i, c := range configs { for i, c := range configs {
t.Log("Config", i, c) t.Log("Config", i, c)
wd, state := setup(t) wd, state := setupUfs(t)
if c.f1_ro { if c.f1_ro {
writeToFile(wd+"/ro/file1", "c1") writeToFile(wd+"/ro/file1", "c1")
} }
...@@ -361,7 +360,7 @@ func TestRename(t *testing.T) { ...@@ -361,7 +360,7 @@ func TestRename(t *testing.T) {
func TestWritableDir(t *testing.T) { func TestWritableDir(t *testing.T) {
t.Log("TestWritableDir") t.Log("TestWritableDir")
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
dirname := wd + "/ro/subdir" dirname := wd + "/ro/subdir"
...@@ -377,7 +376,7 @@ func TestWritableDir(t *testing.T) { ...@@ -377,7 +376,7 @@ func TestWritableDir(t *testing.T) {
func TestTruncate(t *testing.T) { func TestTruncate(t *testing.T) {
t.Log("TestTruncate") t.Log("TestTruncate")
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
writeToFile(wd+"/ro/file", "hello") writeToFile(wd+"/ro/file", "hello")
...@@ -394,7 +393,7 @@ func TestTruncate(t *testing.T) { ...@@ -394,7 +393,7 @@ func TestTruncate(t *testing.T) {
func TestCopyChmod(t *testing.T) { func TestCopyChmod(t *testing.T) {
t.Log("TestCopyChmod") t.Log("TestCopyChmod")
wd, state := setup(t) wd, state := setupUfs(t)
defer state.Unmount() defer state.Unmount()
contents := "hello" contents := "hello"
......
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