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)
......
......@@ -5,7 +5,7 @@ package fuse
PathFilesystemConnector is a lowlevel FUSE filesystem that translates
from inode numbers (as delivered by the kernel) to traditional path
names. The paths are then used as arguments for methods of
PathFilesystem instances.
PathFilesystem instances.
PathFilesystemConnector supports mounts of different PathFilesystem
on top of each other's directories.
......@@ -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
time.AfterFunc(0.1e9, func() { me.updateKnownFses() })
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
}
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()
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
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)
}
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 {
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)
......@@ -48,7 +47,7 @@ func setup(t *testing.T) (workdir string, state *fuse.MountState) {
AttrTimeout: entryTtl,
NegativeTimeout: entryTtl,
}
connector := fuse.NewFileSystemConnector(ufs, opts)
state = fuse.NewMountState(connector)
state.Mount(wd + "/mount")
......@@ -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,9 +133,9 @@ 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")
err := os.Chtimes(wd + "/ro/file", 42e9, 43e9)
CheckSuccess(err)
......@@ -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"
......@@ -165,7 +164,7 @@ func TestChmod(t *testing.T) {
if code != fuse.EPERM {
t.Error("Unexpected error code", code, err)
}
fi, err := os.Lstat(m_fn)
CheckSuccess(err)
if fi.Mode&07777 != 07070 {
......@@ -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