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

Move MultiZipFs into examplelib, and provide a test. Document.

parent efce7afc
...@@ -5,9 +5,10 @@ TARG=github.com/hanwen/go-fuse/examplelib ...@@ -5,9 +5,10 @@ TARG=github.com/hanwen/go-fuse/examplelib
DEPS=../fuse DEPS=../fuse
GOFILES=passthrough.go\ GOFILES=passthrough.go \
stackfs.go\ stackfs.go \
zipfs.go\ zipfs.go \
multizip.go \
misc.go misc.go
include $(GOROOT)/src/Make.pkg include $(GOROOT)/src/Make.pkg
......
package examplelib
/*
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
*/
import (
"github.com/hanwen/go-fuse/fuse"
"log"
"os"
"path"
"sync"
"strings"
)
var _ = log.Printf
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.DefaultRawFuseFile
}
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 := NewZipFileFuse(zipFile)
if fs == nil {
// TODO
log.Println("NewZipFileFuse returned nil")
me.zfs.pendingZips[me.Basename] = false, false
return 0, fuse.ENOSYS
}
code := me.zfs.Connector.Mount("/"+path.Base(me.Basename), fs)
if code != fuse.OK {
return 0, code
}
// TODO. locks?
me.zfs.zips[me.Basename] = fs
me.zfs.pendingZips[me.Basename] = false, false
me.zfs = nil
return uint32(len(nameBytes)), code
}
////////////////////////////////////////////////////////////////
// MultiZipFs is a path filesystem that mounts zipfiles. It needs a
// reference to the PathFileSystemConnector to be able to execute
// mounts.
type MultiZipFs struct {
Connector *fuse.PathFileSystemConnector
lock sync.RWMutex
zips map[string]*ZipFileFuse
pendingZips map[string]bool
zipFileNames map[string]string
fuse.DefaultPathFilesystem
}
func NewMultiZipFs() *MultiZipFs {
m := new(MultiZipFs)
m.zips = make(map[string]*ZipFileFuse)
m.pendingZips = make(map[string]bool)
m.zipFileNames = make(map[string]string)
m.Connector = fuse.NewPathFileSystemConnector(m)
return m
}
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)
}
if name == "" {
var d fuse.DirEntry
d.Name = "config"
d.Mode = fuse.S_IFDIR | 0700
stream <- fuse.DirEntry(d)
}
stream <- fuse.DirEntry{Name: ""}
return stream, fuse.OK
}
func (me *MultiZipFs) GetAttr(name string) (*fuse.Attr, fuse.Status) {
a := new(fuse.Attr)
if name == "" {
// Should not write in top dir.
a.Mode = fuse.S_IFDIR | 0500
return a, fuse.OK
}
if name == "config" {
// TODO
a.Mode = fuse.S_IFDIR | 0700
return a, fuse.OK
}
dir, base := path.Split(name)
if dir != "" && dir != CONFIG_PREFIX {
return nil, fuse.ENOENT
}
submode := uint32(fuse.S_IFDIR | 0700)
if dir == CONFIG_PREFIX {
submode = fuse.S_IFREG | 0600
}
me.lock.RLock()
defer me.lock.RUnlock()
a.Mode = submode
entry, hasDir := me.zips[base]
if hasDir {
a.Size = uint64(len(entry.ZipFileName))
return a, fuse.OK
}
_, hasDir = me.pendingZips[base]
if hasDir {
return a, fuse.OK
}
return nil, fuse.ENOENT
}
func (me *MultiZipFs) Unlink(name string) (code fuse.Status) {
dir, basename := path.Split(name)
if dir == CONFIG_PREFIX {
me.lock.Lock()
defer me.lock.Unlock()
_, ok := me.zips[basename]
if ok {
me.zips[basename] = nil, false
return fuse.OK
} else {
return fuse.ENOENT
}
}
return fuse.EPERM
}
func (me *MultiZipFs) Open(name string, flags uint32) (file fuse.RawFuseFile, code fuse.Status) {
if 0 != flags&uint32(os.O_WRONLY|os.O_RDWR|os.O_APPEND) {
return nil, fuse.EPERM
}
dir, basename := path.Split(name)
if dir == CONFIG_PREFIX {
me.lock.RLock()
defer me.lock.RUnlock()
entry, ok := me.zips[basename]
if !ok {
return nil, fuse.ENOENT
}
return fuse.NewReadOnlyFile([]byte(entry.ZipFileName)), fuse.OK
}
return nil, fuse.ENOENT
}
func (me *MultiZipFs) Create(name string, flags uint32, mode uint32) (file fuse.RawFuseFile, code fuse.Status) {
dir, base := path.Split(name)
if dir != CONFIG_PREFIX {
return nil, fuse.EPERM
}
z := new(zipCreateFile)
z.Basename = base
z.zfs = me
me.lock.Lock()
defer me.lock.Unlock()
me.pendingZips[z.Basename] = true
return z, fuse.OK
}
package examplelib
import (
"github.com/hanwen/go-fuse/fuse"
"os"
"testing"
"time"
)
func TestMultiZipFs(t *testing.T) {
var err os.Error
wd, err := os.Getwd()
zipFile := wd + "/test.zip"
fs := NewMultiZipFs()
state := fuse.NewMountState(fs.Connector)
mountPoint := fuse.MakeTempDir()
state.Debug = true
err = state.Mount(mountPoint)
CheckSuccess(err)
go state.Loop(true)
f, err := os.Open(mountPoint + "", os.O_RDONLY, 0)
CheckSuccess(err)
names, err := f.Readdirnames(-1)
CheckSuccess(err)
if len(names) != 1 || string(names[0]) != "config" {
t.Errorf("wrong names return. %v", names)
}
err = f.Close()
CheckSuccess(err)
f, err = os.Open(mountPoint + "/random", os.O_WRONLY | os.O_CREATE, 0)
if err == nil {
t.Error("Must fail writing in root.")
}
f, err = os.Open(mountPoint + "/config/zipmount", os.O_WRONLY, 0)
if err == nil {
t.Error("Must fail without O_CREATE")
}
f, err = os.Open(mountPoint + "/config/zipmount", os.O_WRONLY | os.O_CREATE, 0)
CheckSuccess(err)
// Directory exists, but is empty.
if !IsDir(mountPoint + "/zipmount") {
t.Errorf("Expect directory at /zipmount")
}
// Open the zip file.
_, err = f.Write([]byte(zipFile))
CheckSuccess(err)
_, err = f.Write([]byte(zipFile))
if err == nil {
t.Error("Must fail second write.")
}
err = f.Close()
CheckSuccess(err)
if !IsDir(mountPoint + "/zipmount") {
t.Errorf("Expect directory at /zipmount")
}
// 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)
fi, err = os.Stat(mountPoint + "/zipmount")
if err == nil {
t.Error("stat should fail after unmount.", fi)
}
state.Unmount()
}
...@@ -7,212 +7,10 @@ import ( ...@@ -7,212 +7,10 @@ import (
"flag" "flag"
"log" "log"
"os" "os"
"path"
"sync"
"strings"
) )
var _ = log.Printf var _ = log.Printf
const (
CONFIG_PREFIX = "config/"
)
type ZipCreateFile struct {
// Basename of the entry in the FS.
Basename string
zfs *MultiZipFs
fuse.DefaultRawFuseFile
}
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 := examplelib.NewZipFileFuse(zipFile)
if fs == nil {
// TODO
log.Println("NewZipFileFuse returned nil")
me.zfs.pendingZips[me.Basename] = false, false
return 0, fuse.ENOSYS
}
code := me.zfs.Connector.Mount("/"+path.Base(me.Basename), fs)
if code != fuse.OK {
return 0, code
}
// TODO. locks?
me.zfs.zips[me.Basename] = fs
me.zfs.pendingZips[me.Basename] = false, false
me.zfs = nil
return uint32(len(nameBytes)), code
}
////////////////////////////////////////////////////////////////
type MultiZipFs struct {
Connector *fuse.PathFileSystemConnector
lock sync.RWMutex
zips map[string]*examplelib.ZipFileFuse
pendingZips map[string]bool
zipFileNames map[string]string
fuse.DefaultPathFilesystem
}
func NewMultiZipFs() *MultiZipFs {
m := new(MultiZipFs)
m.zips = make(map[string]*examplelib.ZipFileFuse)
m.pendingZips = make(map[string]bool)
m.zipFileNames = make(map[string]string)
m.Connector = fuse.NewPathFileSystemConnector(m)
return m
}
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)
}
if name == "" {
var d fuse.DirEntry
d.Name = "config"
d.Mode = fuse.S_IFDIR | 0700
stream <- fuse.DirEntry(d)
}
stream <- fuse.DirEntry{Name: ""}
return stream, fuse.OK
}
func (me *MultiZipFs) GetAttr(name string) (*fuse.Attr, fuse.Status) {
a := new(fuse.Attr)
if name == "" {
// Should not write in top dir.
a.Mode = fuse.S_IFDIR | 0500
return a, fuse.OK
}
if name == "config" {
// TODO
a.Mode = fuse.S_IFDIR | 0700
return a, fuse.OK
}
dir, base := path.Split(name)
if dir != "" && dir != CONFIG_PREFIX {
return nil, fuse.ENOENT
}
submode := uint32(fuse.S_IFDIR | 0700)
if dir == CONFIG_PREFIX {
submode = fuse.S_IFREG | 0600
}
me.lock.RLock()
defer me.lock.RUnlock()
a.Mode = submode
entry, hasDir := me.zips[base]
if hasDir {
a.Size = uint64(len(entry.ZipFileName))
return a, fuse.OK
}
_, hasDir = me.pendingZips[base]
if hasDir {
return a, fuse.OK
}
return nil, fuse.ENOENT
}
func (me *MultiZipFs) Unlink(name string) (code fuse.Status) {
dir, basename := path.Split(name)
if dir == CONFIG_PREFIX {
me.lock.Lock()
defer me.lock.Unlock()
_, ok := me.zips[basename]
if ok {
me.zips[basename] = nil, false
return fuse.OK
} else {
return fuse.ENOENT
}
}
return fuse.EPERM
}
func (me *MultiZipFs) Open(name string, flags uint32) (file fuse.RawFuseFile, code fuse.Status) {
if 0 != flags&uint32(os.O_WRONLY|os.O_RDWR|os.O_APPEND) {
return nil, fuse.EPERM
}
dir, basename := path.Split(name)
if dir == CONFIG_PREFIX {
me.lock.RLock()
defer me.lock.RUnlock()
entry, ok := me.zips[basename]
if !ok {
return nil, fuse.ENOENT
}
return fuse.NewReadOnlyFile([]byte(entry.ZipFileName)), fuse.OK
}
return nil, fuse.ENOENT
}
func (me *MultiZipFs) Create(name string, flags uint32, mode uint32) (file fuse.RawFuseFile, code fuse.Status) {
dir, base := path.Split(name)
if dir != CONFIG_PREFIX {
return nil, fuse.EPERM
}
z := new(ZipCreateFile)
z.Basename = base
z.zfs = me
me.lock.Lock()
defer me.lock.Unlock()
me.pendingZips[z.Basename] = true
return z, fuse.OK
}
func main() { func main() {
// Scans the arg list and sets up flags // Scans the arg list and sets up flags
flag.Parse() flag.Parse()
...@@ -222,7 +20,7 @@ func main() { ...@@ -222,7 +20,7 @@ func main() {
os.Exit(2) os.Exit(2)
} }
fs := NewMultiZipFs() fs := examplelib.NewMultiZipFs()
state := fuse.NewMountState(fs.Connector) state := fuse.NewMountState(fs.Connector)
mountPoint := flag.Arg(0) mountPoint := flag.Arg(0)
......
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