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)
......
...@@ -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
if me.options.UpdateOnMount {
time.AfterFunc(0.1e9, func() { me.updateKnownFses() }) 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
} }
me.lock.Lock()
var gofs *UnionFs var gofs *UnionFs
if me.knownFileSystems[name] == nil { if me.knownFileSystems[name] == nil {
log.Println("Adding UnionFs for roots", roots) log.Println("Adding UnionFs for roots", roots)
gofs = NewUnionFs(roots, me.options.UnionFsOptions) gofs = NewUnionFs(roots, me.options.UnionFsOptions)
me.knownFileSystems[name] = gofs 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 { 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)
...@@ -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,7 +133,7 @@ func TestSymlink(t *testing.T) { ...@@ -134,7 +133,7 @@ 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")
...@@ -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"
...@@ -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