Commit 4a79a89d authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Rewrite Mount/Unmount logic.

* In-process mounts are now always on synthetic inodes.  FileSystems
  no longer have to generate GetAttr() responses for mount points.

  Advantages:

  - Simplifies FileSystems: there is no need to represent the state
    between mountpoint appearing and Mount() succeeding, and mount
    points appear automatically in ReadDir().

  - Mount point inodes are always associated with one filesystem over
    their lifetime.  This simplifies synchronization around unmount calls,
    which could previously lead to "unlock of unlocked mutex" panics.

* (Un)mount always takes 2 locks: treeLock from the parent, and the
  treeLock of the filesystem itself, as the parent/child relation
  crosses the boundary between filesystems..

* Remove unmountPending variable; Unmount now removes all known inodes
  directly, removing reliance on eventual consistency.

* Document Mount(), Unmount()

* Forced forgets (eg. when fusermount -u is called) will also cause
  FileSystem.Unmount() to be called.

* Changes to accomodate new conventions in ZipFs/UnionFs

* Configure MultiZipFs using symlinks rather than catching file
  writes.

* Remove return from Mount() api call.
parent 6a591a3e
......@@ -44,7 +44,7 @@ type FileSystem interface {
SetXAttr(name string, attr string, data []byte, flags int) Status
// Called after mount.
Mount(connector *FileSystemConnector) Status
Mount(connector *FileSystemConnector)
Unmount()
// File handling. If opening for writing, the file's mtime
......
......@@ -260,8 +260,7 @@ func (me *DefaultFileSystem) OpenDir(name string) (stream chan DirEntry, status
return nil, ENOSYS
}
func (me *DefaultFileSystem) Mount(conn *FileSystemConnector) Status {
return OK
func (me *DefaultFileSystem) Mount(conn *FileSystemConnector) {
}
func (me *DefaultFileSystem) Unmount() {
......
......@@ -66,44 +66,47 @@ func (me *DirEntryList) Bytes() []byte {
////////////////////////////////////////////////////////////////
type Dir struct {
extra []DirEntry
stream chan DirEntry
leftOver DirEntry
}
func (me *Dir) inode(name string) uint64 {
func (me *Dir) ReadDir(input *ReadIn) (*DirEntryList, Status) {
if me.stream == nil && len(me.extra) == 0 {
return nil, OK
}
// We could also return
// me.connector.lookupUpdate(me.parentIno, name).NodeId but it
// appears FUSE will issue a LOOKUP afterwards for the entry
// anyway, so we skip hash table update here.
return FUSE_UNKNOWN_INO
}
func (me *Dir) ReadDir(input *ReadIn) (*DirEntryList, Status) {
if me.stream == nil {
return nil, OK
}
inode := uint64(FUSE_UNKNOWN_INO)
list := NewDirEntryList(int(input.Size))
if me.leftOver.Name != "" {
n := me.leftOver.Name
i := me.inode(n)
success := list.AddString(n, i, me.leftOver.Mode)
success := list.AddString(n, inode, me.leftOver.Mode)
if !success {
panic("No space for single entry.")
}
me.leftOver.Name = ""
}
for len(me.extra) > 0 {
e := me.extra[len(me.extra)-1]
me.extra = me.extra[:len(me.extra)-1]
success := list.AddString(e.Name, inode, e.Mode)
if !success {
me.leftOver = e
return list, OK
}
}
for {
d, isOpen := <-me.stream
if !isOpen {
me.stream = nil
break
}
i := me.inode(d.Name)
if !list.AddString(d.Name, i, d.Mode) {
if !list.AddString(d.Name, inode, d.Mode) {
me.leftOver = d
break
}
......
......@@ -95,9 +95,9 @@ func (me *LockingFileSystem) OpenDir(name string) (stream chan DirEntry, status
return me.FileSystem.OpenDir(name)
}
func (me *LockingFileSystem) Mount(conn *FileSystemConnector) Status {
func (me *LockingFileSystem) Mount(conn *FileSystemConnector) {
defer me.locked()()
return me.FileSystem.Mount(conn)
me.FileSystem.Mount(conn)
}
func (me *LockingFileSystem) Unmount() {
......
......@@ -114,9 +114,9 @@ func (me *LoggingFileSystem) OpenDir(name string) (stream chan DirEntry, status
return me.FileSystem.OpenDir(name)
}
func (me *LoggingFileSystem) Mount(conn *FileSystemConnector) Status {
func (me *LoggingFileSystem) Mount(conn *FileSystemConnector) {
me.Print("Mount", "")
return me.FileSystem.Mount(conn)
me.FileSystem.Mount(conn)
}
func (me *LoggingFileSystem) Unmount() {
......
......@@ -11,7 +11,6 @@ import (
"strings"
"syscall"
"testing"
"time"
)
var _ = strings.Join
......@@ -618,6 +617,19 @@ func TestLargeDirRead(t *testing.T) {
ts.testLargeDirRead()
}
func TestRootDir(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
d, err := os.Open(ts.mountPoint)
CheckSuccess(err)
_, err = d.Readdirnames(-1)
CheckSuccess(err)
err = d.Close()
CheckSuccess(err)
}
func TestDelRename(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
......@@ -646,97 +658,3 @@ func TestIoctl(t *testing.T) {
fmt.Println("ioctl", v, e)
}
func TestRecursiveMount(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
f, err := os.OpenFile(filepath.Join(ts.mountPoint, "hello.txt"),
os.O_WRONLY|os.O_CREATE, 0777)
CheckSuccess(err)
f.WriteString("bla")
f.Close()
pfs2 := NewLoopbackFileSystem(ts.origDir)
code := ts.connector.Mount("/hello.txt", pfs2, nil)
if code != EINVAL {
t.Error("expect EINVAL", code)
}
submnt := filepath.Join(ts.mountPoint, "mnt")
err = os.Mkdir(submnt, 0777)
CheckSuccess(err)
code = ts.connector.Mount("/mnt", pfs2, nil)
if code != OK {
t.Errorf("mkdir")
}
_, err = os.Lstat(submnt)
CheckSuccess(err)
_, err = os.Lstat(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
f, err = os.Open(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
code = ts.connector.Unmount("/mnt")
if code != EBUSY {
t.Error("expect EBUSY")
}
err = os.Rename(ts.mountPoint+"/mnt", ts.mountPoint+"/foobar")
CheckSuccess(err)
f.Close()
log.Println("Waiting for kernel to flush file-close to fuse...")
time.Sleep(1.5e9 * testTtl)
code = ts.connector.Unmount("/doesnotexist")
if code != EINVAL {
t.Fatal("expect EINVAL", code)
}
code = ts.connector.Unmount("/foobar")
if code != OK {
t.Error("umount failed.", code)
}
}
func TestDeletedUnmount(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
submnt := filepath.Join(ts.mountPoint, "mnt")
err := os.Mkdir(submnt, 0777)
CheckSuccess(err)
pfs2 := NewLoopbackFileSystem(ts.origDir)
code := ts.connector.Mount("/mnt", pfs2, nil)
if !code.Ok() {
t.Fatal("err")
}
f, err := os.Create(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
log.Println("Removing")
err = os.Remove(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
log.Println("Removing")
_, err = f.Write([]byte("bla"))
CheckSuccess(err)
code = ts.connector.Unmount("/mnt")
if code != EBUSY {
t.Error("expect EBUSY", code)
}
f.Close()
time.Sleep(1.5e9 * testTtl)
code = ts.connector.Unmount("/mnt")
if !code.Ok() {
t.Error("should succeed", code)
}
}
package fuse
import (
"log"
"os"
"testing"
"time"
"path/filepath"
"io/ioutil"
)
func TestMountOnExisting(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
err := os.Mkdir(ts.mountPoint + "/mnt", 0777)
CheckSuccess(err)
fs := &DefaultFileSystem{}
code := ts.connector.Mount("/mnt", fs, nil)
if code != EBUSY {
t.Fatal("expect EBUSY:", code)
}
err = os.Remove(ts.mountPoint + "/mnt")
CheckSuccess(err)
code = ts.connector.Mount("/mnt", fs, nil)
if !code.Ok() {
t.Fatal("expect OK:", code)
}
}
func TestUnmountNoExist(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
code := ts.connector.Unmount("/doesnotexist")
if code != EINVAL {
t.Fatal("expect EINVAL", code)
}
}
func TestMountRename(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
fs := NewLoopbackFileSystem(ts.origDir)
code := ts.connector.Mount("/mnt", fs, nil)
if !code.Ok() {
t.Fatal("mount should succeed")
}
err := os.Rename(ts.mountPoint+"/mnt", ts.mountPoint+"/foobar")
if OsErrorToErrno(err) != EBUSY {
t.Fatal("rename mount point should fail with EBUSY:", err)
}
}
func TestMountReaddir(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
fs := NewLoopbackFileSystem(ts.origDir)
code := ts.connector.Mount("/mnt", fs, nil)
if !code.Ok() {
t.Fatal("mount should succeed")
}
entries, err := ioutil.ReadDir(ts.mountPoint)
CheckSuccess(err)
if len(entries) != 1 || entries[0].Name != "mnt" {
t.Error("wrong readdir result", entries)
}
}
func TestRecursiveMount(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
err := ioutil.WriteFile(ts.origDir + "/hello.txt", []byte("blabla"), 0644)
CheckSuccess(err)
fs := NewLoopbackFileSystem(ts.origDir)
code := ts.connector.Mount("/mnt", fs, nil)
if !code.Ok() {
t.Fatal("mount should succeed")
}
submnt := ts.mountPoint + "/mnt"
_, err = os.Lstat(submnt)
CheckSuccess(err)
_, err = os.Lstat(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
f, err := os.Open(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
log.Println("Attempting unmount, should fail")
code = ts.connector.Unmount("/mnt")
if code != EBUSY {
t.Error("expect EBUSY")
}
f.Close()
log.Println("Waiting for kernel to flush file-close to fuse...")
time.Sleep(1.5e9 * testTtl)
log.Println("Attempting unmount, should succeed")
code = ts.connector.Unmount("/mnt")
if code != OK {
t.Error("umount failed.", code)
}
}
func TestDeletedUnmount(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
submnt := filepath.Join(ts.mountPoint, "mnt")
pfs2 := NewLoopbackFileSystem(ts.origDir)
code := ts.connector.Mount("/mnt", pfs2, nil)
if !code.Ok() {
t.Fatal("Mount error", code)
}
f, err := os.Create(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
log.Println("Removing")
err = os.Remove(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
log.Println("Removing")
_, err = f.Write([]byte("bla"))
CheckSuccess(err)
code = ts.connector.Unmount("/mnt")
if code != EBUSY {
t.Error("expect EBUSY for unmount with open files", code)
}
f.Close()
time.Sleep(1.5e9 * testTtl)
code = ts.connector.Unmount("/mnt")
if !code.Ok() {
t.Error("should succeed", code)
}
}
This diff is collapsed.
......@@ -37,11 +37,17 @@ func (me *FileSystemConnector) internalLookup(parent *inode, name string, lookup
}
func (me *FileSystemConnector) internalLookupWithNode(parent *inode, name string, lookupCount int) (out *EntryOut, status Status, node *inode) {
// TODO - fuse.c has special case code for name == "." and
// "..", those lookups happen if FUSE_EXPORT_SUPPORT is set in
// Init.
fullPath, mount := parent.GetPath()
fullPath, mount, isMountPoint := me.lookupMount(parent, name, lookupCount)
if isMountPoint {
node = mount.mountPoint
} else {
fullPath, mount = parent.GetPath()
fullPath = filepath.Join(fullPath, name)
}
if mount == nil {
fmt.Println(me.rootNode)
fmt.Println(me.rootNode.mountPoint)
timeout := me.rootNode.mountPoint.options.NegativeTimeout
if timeout > 0 {
return NegativeEntry(timeout), OK, nil
......@@ -49,10 +55,7 @@ func (me *FileSystemConnector) internalLookupWithNode(parent *inode, name string
return nil, ENOENT, nil
}
}
fullPath = filepath.Join(fullPath, name)
fi, err := mount.fs.GetAttr(fullPath)
if err == ENOENT && mount.options.NegativeTimeout > 0.0 {
return NegativeEntry(mount.options.NegativeTimeout), OK, nil
}
......@@ -60,11 +63,12 @@ func (me *FileSystemConnector) internalLookupWithNode(parent *inode, name string
if err != OK {
return nil, err, nil
}
data := me.lookupUpdate(parent, name, fi.IsDirectory(), lookupCount)
if !isMountPoint {
node = me.lookupUpdate(parent, name, fi.IsDirectory(), lookupCount)
}
out = &EntryOut{
NodeId: data.NodeId,
NodeId: node.NodeId,
Generation: 1, // where to get the generation?
}
SplitNs(mount.options.EntryTimeout, &out.EntryValid, &out.EntryValidNsec)
......@@ -74,10 +78,10 @@ func (me *FileSystemConnector) internalLookupWithNode(parent *inode, name string
}
CopyFileInfo(fi, &out.Attr)
out.Attr.Ino = data.NodeId
out.Attr.Ino = node.NodeId
mount.setOwner(&out.Attr)
return out, OK, data
return out, OK, node
}
......@@ -137,6 +141,7 @@ func (me *FileSystemConnector) OpenDir(header *InHeader, input *OpenIn) (flags u
}
de := &Dir{
extra: node.GetMountDirEntries(),
stream: stream,
}
h := mount.registerFileHandle(node, de, nil, input.Flags)
......@@ -328,6 +333,10 @@ func (me *FileSystemConnector) Rename(header *InHeader, input *RenameIn, oldName
if mount == nil || oldMount == nil {
return ENOENT
}
_, _, isMountPoint := me.lookupMount(oldParent, oldName, 0)
if isMountPoint {
return EBUSY
}
if mount != oldMount {
return EXDEV
}
......@@ -382,6 +391,10 @@ func (me *FileSystemConnector) Create(header *InHeader, input *CreateIn, name st
}
out, code, inode := me.internalLookupWithNode(parent, name, 1)
if inode == nil {
msg := fmt.Sprintf("Create succeded, but GetAttr returned no entry %v", fullPath)
panic(msg)
}
return 0, mount.registerFileHandle(inode, nil, f, input.Flags), out, code
}
......
......@@ -135,9 +135,9 @@ func (me *TimingFileSystem) OpenDir(name string) (stream chan DirEntry, status S
return me.FileSystem.OpenDir(name)
}
func (me *TimingFileSystem) Mount(conn *FileSystemConnector) Status {
func (me *TimingFileSystem) Mount(conn *FileSystemConnector) {
defer me.startTimer("Mount", "")()
return me.FileSystem.Mount(conn)
me.FileSystem.Mount(conn)
}
func (me *TimingFileSystem) Unmount() {
......
......@@ -60,12 +60,11 @@ func NewAutoUnionFs(directory string, options AutoUnionFsOptions) *AutoUnionFs {
return a
}
func (me *AutoUnionFs) Mount(connector *fuse.FileSystemConnector) fuse.Status {
func (me *AutoUnionFs) Mount(connector *fuse.FileSystemConnector) {
me.connector = connector
if me.options.UpdateOnMount {
time.AfterFunc(0.1e9, func() { me.updateKnownFses() })
}
return fuse.OK
}
func (me *AutoUnionFs) addAutomaticFs(roots []string) {
......@@ -343,15 +342,12 @@ func (me *AutoUnionFs) OpenDir(name string) (stream chan fuse.DirEntry, status f
defer me.lock.RUnlock()
stream = make(chan fuse.DirEntry, len(me.knownFileSystems)+5)
for k, _ := range me.knownFileSystems {
mode := fuse.S_IFDIR | 0755
if name == _CONFIG {
mode = syscall.S_IFLNK | 0644
}
stream <- fuse.DirEntry{
Name: k,
Mode: uint32(mode),
if name == _CONFIG {
for k, _ := range me.knownFileSystems {
stream <- fuse.DirEntry{
Name: k,
Mode: syscall.S_IFLNK | 0644,
}
}
}
......
......@@ -74,6 +74,7 @@ func TestAutoFsSymlink(t *testing.T) {
err := os.Mkdir(wd+"/store/backing1", 0755)
CheckSuccess(err)
os.Symlink(wd+"/ro", wd+"/store/backing1/READONLY")
CheckSuccess(err)
......@@ -83,6 +84,12 @@ func TestAutoFsSymlink(t *testing.T) {
fi, err := os.Lstat(wd + "/mount/manual1/file1")
CheckSuccess(err)
entries, err := ioutil.ReadDir(wd + "/mount")
CheckSuccess(err)
if len(entries) != 3 {
t.Error("readdir mismatch", entries)
}
err = os.Remove(wd + "/mount/config/manual1")
CheckSuccess(err)
......
......@@ -5,8 +5,8 @@ package zipfs
This provides a practical example of mounting Go-fuse path filesystems
on top of each other.
It is a file system that configures a Zip filesystem at /zipmount when writing
path/to/zipfile to /config/zipmount
It is a file system that configures a Zip filesystem at /zipmount when
symlinking path/to/zipfile to /config/zipmount
*/
......@@ -16,7 +16,6 @@ import (
"os"
"path/filepath"
"sync"
"strings"
)
var _ = log.Printf
......@@ -25,47 +24,6 @@ const (
CONFIG_PREFIX = "config/"
)
// zipCreateFile is a placeholder file to receive the write containing
// the path to the zip file.
type zipCreateFile struct {
// Basename of the entry in the FS.
Basename string
zfs *MultiZipFs
fuse.DefaultFile
}
func (me *zipCreateFile) Write(input *fuse.WriteIn, nameBytes []byte) (uint32, fuse.Status) {
if me.zfs == nil {
// TODO
return 0, fuse.EPERM
}
zipFile := string(nameBytes)
zipFile = strings.Trim(zipFile, "\n ")
fs, err := NewArchiveFileSystem(zipFile)
if err != nil {
// TODO
log.Println("NewZipArchiveFileSystem failed.")
me.zfs.pendingZips[me.Basename] = false, false
return 0, fuse.ENOSYS
}
code := me.zfs.Connector.Mount("/"+filepath.Base(me.Basename), fs, nil)
if code != fuse.OK {
return 0, code
}
// TODO. locks?
me.zfs.zips[me.Basename] = fs
me.zfs.dirZipFileMap[me.Basename] = zipFile
me.zfs.pendingZips[me.Basename] = false, false
me.zfs = nil
return uint32(len(nameBytes)), code
}
////////////////////////////////////////////////////////////////
......@@ -76,7 +34,6 @@ type MultiZipFs struct {
Connector *fuse.FileSystemConnector
lock sync.RWMutex
zips map[string]*MemTreeFileSystem
pendingZips map[string]bool
dirZipFileMap map[string]string
fuse.DefaultFileSystem
......@@ -85,43 +42,19 @@ type MultiZipFs struct {
func NewMultiZipFs() *MultiZipFs {
m := new(MultiZipFs)
m.zips = make(map[string]*MemTreeFileSystem)
m.pendingZips = make(map[string]bool)
m.dirZipFileMap = make(map[string]string)
return m
}
func (me *MultiZipFs) Mount(connector *fuse.FileSystemConnector) fuse.Status {
func (me *MultiZipFs) Mount(connector *fuse.FileSystemConnector) {
me.Connector = connector
return fuse.OK
}
func (me *MultiZipFs) OpenDir(name string) (stream chan fuse.DirEntry, code fuse.Status) {
me.lock.RLock()
defer me.lock.RUnlock()
// We don't use a goroutine, since we don't want to hold the
// lock.
stream = make(chan fuse.DirEntry,
len(me.pendingZips)+len(me.zips)+2)
submode := uint32(fuse.S_IFDIR | 0700)
if name == "config" {
submode = fuse.S_IFREG | 0600
}
for k, _ := range me.zips {
var d fuse.DirEntry
d.Name = k
d.Mode = submode
stream <- fuse.DirEntry(d)
}
for k, _ := range me.pendingZips {
var d fuse.DirEntry
d.Name = k
d.Mode = submode
stream <- fuse.DirEntry(d)
}
stream = make(chan fuse.DirEntry, len(me.zips)+2)
if name == "" {
var d fuse.DirEntry
d.Name = "config"
......@@ -129,6 +62,15 @@ func (me *MultiZipFs) OpenDir(name string) (stream chan fuse.DirEntry, code fuse
stream <- fuse.DirEntry(d)
}
if name == "config" {
for k, _ := range me.zips {
var d fuse.DirEntry
d.Name = k
d.Mode = fuse.S_IFLNK
stream <- fuse.DirEntry(d)
}
}
close(stream)
return stream, fuse.OK
}
......@@ -153,7 +95,7 @@ func (me *MultiZipFs) GetAttr(name string) (*os.FileInfo, fuse.Status) {
}
submode := uint32(fuse.S_IFDIR | 0700)
if dir == CONFIG_PREFIX {
submode = fuse.S_IFREG | 0600
submode = fuse.S_IFLNK | 0600
}
me.lock.RLock()
......@@ -164,10 +106,6 @@ func (me *MultiZipFs) GetAttr(name string) (*os.FileInfo, fuse.Status) {
if hasDir {
return a, fuse.OK
}
_, hasDir = me.pendingZips[base]
if hasDir {
return a, fuse.OK
}
return nil, fuse.ENOENT
}
......@@ -180,6 +118,10 @@ func (me *MultiZipFs) Unlink(name string) (code fuse.Status) {
_, ok := me.zips[basename]
if ok {
code = me.Connector.Unmount("/" + basename)
if !code.Ok() {
return code
}
me.zips[basename] = nil, false
me.dirZipFileMap[basename] = "", false
return fuse.OK
......@@ -190,41 +132,48 @@ func (me *MultiZipFs) Unlink(name string) (code fuse.Status) {
return fuse.EPERM
}
func (me *MultiZipFs) Open(name string, flags uint32) (file fuse.File, code fuse.Status) {
if 0 != flags&uint32(fuse.O_ANYWRITE) {
return nil, fuse.EPERM
func (me *MultiZipFs) Readlink(path string) (val string, code fuse.Status) {
dir, base := filepath.Split(path)
if dir != CONFIG_PREFIX {
return "", fuse.ENOENT
}
dir, basename := filepath.Split(name)
if dir == CONFIG_PREFIX {
me.lock.RLock()
defer me.lock.RUnlock()
orig, ok := me.dirZipFileMap[basename]
if !ok {
return nil, fuse.ENOENT
}
me.lock.Lock()
defer me.lock.Unlock()
return fuse.NewReadOnlyFile([]byte(orig)), fuse.OK
zipfile, ok := me.dirZipFileMap[base]
if !ok {
return "", fuse.ENOENT
}
return zipfile, fuse.OK
return nil, fuse.ENOENT
}
func (me *MultiZipFs) Create(name string, flags uint32, mode uint32) (file fuse.File, code fuse.Status) {
dir, base := filepath.Split(name)
func (me *MultiZipFs) Symlink(value string, linkName string) (code fuse.Status) {
dir, base := filepath.Split(linkName)
if dir != CONFIG_PREFIX {
return nil, fuse.EPERM
return fuse.EPERM
}
z := new(zipCreateFile)
z.Basename = base
z.zfs = me
me.lock.Lock()
defer me.lock.Unlock()
me.pendingZips[z.Basename] = true
_, ok := me.dirZipFileMap[base]
if ok {
return fuse.EBUSY
}
return z, fuse.OK
fs, err := NewArchiveFileSystem(value)
if err != nil {
log.Println("NewZipArchiveFileSystem failed.", err)
return fuse.EINVAL
}
code = me.Connector.Mount("/"+base, fs, nil)
if !code.Ok() {
return code
}
me.dirZipFileMap[base] = value
me.zips[base] = fs
return fuse.OK
}
......@@ -2,6 +2,7 @@ package zipfs
import (
"github.com/hanwen/go-fuse/fuse"
"io/ioutil"
"log"
"os"
"testing"
......@@ -13,85 +14,84 @@ var CheckSuccess = fuse.CheckSuccess
const testTtl = 0.1
func TestMultiZipFs(t *testing.T) {
var err os.Error
wd, err := os.Getwd()
zipFile := wd + "/test.zip"
func setupMzfs() (mountPoint string, cleanup func()) {
fs := NewMultiZipFs()
mountPoint := fuse.MakeTempDir()
mountPoint = fuse.MakeTempDir()
state, _, err := fuse.MountFileSystem(mountPoint, fs, &fuse.FileSystemOptions{
EntryTimeout: testTtl,
AttrTimeout: testTtl,
NegativeTimeout: 0.0,
})
defer os.RemoveAll(mountPoint)
CheckSuccess(err)
defer state.Unmount()
state.Debug = true
go state.Loop(true)
f, err := os.Open(mountPoint + "")
CheckSuccess(err)
names, err := f.Readdirnames(-1)
CheckSuccess(err)
if len(names) != 1 || string(names[0]) != "config" {
t.Errorf("wrong names return. %v", names)
return mountPoint, func() {
state.Unmount()
os.RemoveAll(mountPoint)
}
err = f.Close()
CheckSuccess(err)
}
f, err = os.Create(mountPoint + "/random")
func TestMultiZipReadonly(t *testing.T) {
mountPoint, cleanup := setupMzfs()
defer cleanup()
_, err := os.Create(mountPoint + "/random")
if err == nil {
t.Error("Must fail writing in root.")
}
f, err = os.OpenFile(mountPoint+"/config/zipmount", os.O_WRONLY, 0)
_, err = os.OpenFile(mountPoint+"/config/zipmount", os.O_WRONLY, 0)
if err == nil {
t.Error("Must fail without O_CREATE")
}
f, err = os.Create(mountPoint + "/config/zipmount")
}
func TestMultiZipFs(t *testing.T) {
mountPoint, cleanup := setupMzfs()
defer cleanup()
wd, err := os.Getwd()
zipFile := wd + "/test.zip"
entries, err := ioutil.ReadDir(mountPoint)
CheckSuccess(err)
if len(entries) != 1 || string(entries[0].Name) != "config" {
t.Errorf("wrong names return. %v", entries)
}
err = os.Symlink(zipFile, mountPoint + "/config/zipmount")
CheckSuccess(err)
// Directory exists, but is empty.
fi, err := os.Lstat(mountPoint + "/zipmount")
CheckSuccess(err)
if !fi.IsDirectory() {
t.Errorf("Expect directory at /zipmount")
}
// Open the zip file.
_, err = f.Write([]byte(zipFile))
entries, err = ioutil.ReadDir(mountPoint)
CheckSuccess(err)
_, err = f.Write([]byte(zipFile))
if err == nil {
t.Error("Must fail second write.")
if len(entries) != 2 {
t.Error("Expect 2 entries", entries)
}
err = f.Close()
val, err := os.Readlink(mountPoint + "/config/zipmount")
CheckSuccess(err)
fi, err = os.Lstat(mountPoint + "/zipmount")
if !fi.IsDirectory() {
t.Errorf("Expect directory at /zipmount")
if val != zipFile {
t.Errorf("expected %v got %v", zipFile, val)
}
// Check that zipfs itself works.
fi, err = os.Stat(mountPoint + "/zipmount/subdir")
CheckSuccess(err)
if !fi.IsDirectory() {
t.Error("directory type", fi)
}
}
// Removing the config dir unmount
err = os.Remove(mountPoint + "/config/zipmount")
CheckSuccess(err)
// This is ugly but necessary: We don't have ways to signal
// back to FUSE that the file disappeared.
time.Sleep(1.5e9 * testTtl)
......
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