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() {
AttrTimeout: 1.0,
NegativeTimeout: 1.0,
},
UpdateOnMount: true,
}
gofs := unionfs.NewAutoUnionFs(flag.Arg(1), options)
......
......@@ -535,5 +535,4 @@ func (me *FileSystemConnector) getOpenFileData(nodeid uint64, fh uint64) (f File
}
return
}
......@@ -32,6 +32,9 @@ type AutoUnionFs struct {
type AutoUnionFsOptions struct {
UnionFsOptions
fuse.MountOptions
// If set, run updateKnownFses() after mounting.
UpdateOnMount bool
}
const (
......@@ -52,7 +55,9 @@ func NewAutoUnionFs(directory string, options AutoUnionFsOptions) *AutoUnionFs {
func (me *AutoUnionFs) Mount(connector *fuse.FileSystemConnector) fuse.Status {
me.connector = connector
if me.options.UpdateOnMount {
time.AfterFunc(0.1e9, func() { me.updateKnownFses() })
}
return fuse.OK
}
......@@ -62,25 +67,61 @@ func (me *AutoUnionFs) addAutomaticFs(roots []string) {
me.addFs(name, roots)
}
func (me *AutoUnionFs) addFs(name string, roots []string) bool {
if name == _CONFIG || name == _STATUS {
log.Println("Illegal name for overlay", roots)
return false
func (me *AutoUnionFs) createFs(name string, roots []string) (*UnionFs, fuse.Status) {
me.lock.Lock()
defer me.lock.Unlock()
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
}
me.lock.Lock()
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
}
me.lock.Unlock()
return gofs, fuse.OK
}
func (me *AutoUnionFs) rmFs(name string) (code fuse.Status) {
me.lock.Lock()
defer me.lock.Unlock()
fs := me.knownFileSystems[name]
if fs == nil {
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)
}
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 {
me.connector.Mount("/"+name, gofs, &me.options.MountOptions)
}
return true
return code
}
// TODO - should hide these methods.
......@@ -152,15 +193,26 @@ func (me *AutoUnionFs) Symlink(pointedTo string, linkName string) (code fuse.Sta
}
name := comps[1]
if !me.addFs(name, roots) {
return fuse.EPERM
}
return fuse.OK
return me.addFs(name, roots)
}
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.
func (me *AutoUnionFs) GetXAttr(name string, attr string) ([]byte, fuse.Status) {
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 (
var _ = fmt.Print
var _ = log.Print
var CheckSuccess = fuse.CheckSuccess
func TestFilePathHash(t *testing.T) {
......@@ -19,15 +20,13 @@ func TestFilePathHash(t *testing.T) {
t.Log(filePathHash("xyz/abc"))
}
const entryTtl = 1
var testOpts = UnionFsOptions{
DeletionCacheTTLSecs: entryTtl,
DeletionDirName: "DELETIONS",
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()
err := os.Mkdir(wd+"/mount", 0700)
fuse.CheckSuccess(err)
......@@ -119,7 +118,7 @@ func remove(path string) {
}
func TestSymlink(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
err := os.Symlink("/foobar", wd+"/mount/link")
......@@ -134,7 +133,7 @@ func TestSymlink(t *testing.T) {
}
func TestChtimes(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
writeToFile(wd+"/ro/file", "a")
......@@ -151,7 +150,7 @@ func TestChtimes(t *testing.T) {
}
func TestChmod(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
ro_fn := wd + "/ro/file"
......@@ -178,7 +177,7 @@ func TestChmod(t *testing.T) {
}
func TestBasic(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
writeToFile(wd+"/rw/rw", "a")
......@@ -241,7 +240,7 @@ func TestBasic(t *testing.T) {
}
func TestPromote(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
err := os.Mkdir(wd+"/ro/subdir", 0755)
......@@ -251,7 +250,7 @@ func TestPromote(t *testing.T) {
}
func TestCreate(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
err := os.MkdirAll(wd+"/ro/subdir/sub2", 0755)
......@@ -262,7 +261,7 @@ func TestCreate(t *testing.T) {
}
func TestOpenUndeletes(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
writeToFile(wd+"/ro/file", "X")
......@@ -274,7 +273,7 @@ func TestOpenUndeletes(t *testing.T) {
}
func TestMkdir(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
dirname := wd + "/mount/subdir"
......@@ -286,7 +285,7 @@ func TestMkdir(t *testing.T) {
}
func TestMkdirPromote(t *testing.T) {
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
dirname := wd + "/ro/subdir/subdir2"
......@@ -321,7 +320,7 @@ func TestRename(t *testing.T) {
for i, c := range configs {
t.Log("Config", i, c)
wd, state := setup(t)
wd, state := setupUfs(t)
if c.f1_ro {
writeToFile(wd+"/ro/file1", "c1")
}
......@@ -361,7 +360,7 @@ func TestRename(t *testing.T) {
func TestWritableDir(t *testing.T) {
t.Log("TestWritableDir")
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
dirname := wd + "/ro/subdir"
......@@ -377,7 +376,7 @@ func TestWritableDir(t *testing.T) {
func TestTruncate(t *testing.T) {
t.Log("TestTruncate")
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
writeToFile(wd+"/ro/file", "hello")
......@@ -394,7 +393,7 @@ func TestTruncate(t *testing.T) {
func TestCopyChmod(t *testing.T) {
t.Log("TestCopyChmod")
wd, state := setup(t)
wd, state := setupUfs(t)
defer state.Unmount()
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