Commit 842bb1b0 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Reorganization

* update README

* Split off zipfs into zipfs/ directory

* Remove marginally useful stackfs example
parent a54bca4b
GO-FUSE: native bindings for the FUSE kernel module.
GETTING STARTED
HIGHLIGHTS
* High speed: within a factor 2 of C/C++ based filesystems on
filesystem benchmarks, using the gc compiler. For many real world
applications, the difference will be negligible.
* Supports in-process mounting of different PathFileSystems onto each
other.
* Includes two fleshed out examples, zipfs and unionfs.
EXAMPLES
* examplelib/zipfs.go contains a small and simple read-only filesystem
for zip files. The corresponding command is in example/zipfs/ . For
......@@ -12,6 +24,9 @@ GETTING STARTED
ls /tmp/mountpoint
fusermount -u /tmp/mountpoint
* examplelib/multizipfs.go shows how to use in-process mounts to
combine multiple Go-FUSE filesystems into a larger filesystem.
* fuse/loopback.go supports all the implemented functionality. A
binary to run is in example/loopback/ . For example
......@@ -20,7 +35,21 @@ GETTING STARTED
ls /tmp/mountpoint
fusermount -u /tmp/mountpoint
* unionfs/unionfs.go: implements a union mount using 1 R/W branch, and
multiple R/O branches.
mkdir -p /tmp/mountpoint /tmp/writable
example/unionfs/unionfs /tmp/mountpoint /tmp/writable /usr &
ls /tmp/mountpoint
ls -l /tmp/mountpoint/bin/vi
rm /tmp/mountpoint/bin/vi
ls -l /tmp/mountpoint/bin/vi
cat /tmp/writable/*DELETION*/*
* union/autounionfs.go: creates UnionFs mounts automatically based on
existence of READONLY symlinks.
Tested on:
- x86 32bits (Fedora 14).
......@@ -28,14 +57,6 @@ Tested on:
BENCHMARKS
In benchmarks, Go-fuse's loopback system is less than 2 times slower
than equivalent C++ based FUSE filesystems. In practical applications,
the the file system client processing may mask some of these
performance gaps.
CREDITS
* Inspired by Taru Karttunen's package, https://bitbucket.org/taruti/go-extra.
......
#!/bin/sh
set -eux
for d in fuse examplelib example/loopback example/zipfs \
example/bulkstat example/multizip
for d in fuse zipfs unionfs example/loopback example/zipfs \
example/bulkstat example/multizip example/unionfs \
example/autounionfs ; \
do
gomake -C $d "$@"
done
for d in fuse examplelib
for d in fuse zipfs unionfs
do
(cd $d && gotest )
done
# Use "gomake install" to build and install this package.
include $(GOROOT)/src/Make.inc
TARG=autounionfs
GOFILES=main.go
DEPS=../../fuse ../../unionfs
include $(GOROOT)/src/Make.cmd
package main
import (
"flag"
"fmt"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/unionfs"
"os"
)
func main() {
debug := flag.Bool("debug", false, "debug on")
threaded := flag.Bool("threaded", true, "debug on")
delcache_ttl := flag.Float64("deletion_cache_ttl", 5.0, "Deletion cache TTL in seconds.")
branchcache_ttl := flag.Float64("branchcache_ttl", 5.0, "Branch cache TTL in seconds.")
deldirname := flag.String(
"deletion_dirname", "GOUNIONFS_DELETIONS", "Directory name to use for deletions.")
flag.Parse()
if len(flag.Args()) < 2 {
fmt.Println("Usage:\n main MOUNTPOINT BASEDIR")
os.Exit(2)
}
mountpoint := flag.Arg(0)
ufsOptions := unionfs.UnionFsOptions{
DeletionCacheTTLSecs: *delcache_ttl,
BranchCacheTTLSecs: *branchcache_ttl,
DeletionDirName: *deldirname,
}
options := unionfs.AutoUnionFsOptions{
UnionFsOptions: ufsOptions,
}
gofs := unionfs.NewAutoUnionFs(flag.Arg(1), options)
conn := fuse.NewPathFileSystemConnector(gofs)
mountState := fuse.NewMountState(conn)
mountState.Debug = *debug
fmt.Printf("Mounting...\n")
err := mountState.Mount(mountpoint)
if err != nil {
fmt.Printf("MountFuse fail: %v\n", err)
os.Exit(1)
}
fmt.Printf("Mounted!\n")
mountState.Loop(*threaded)
}
......@@ -3,7 +3,7 @@ include $(GOROOT)/src/Make.inc
TARG=multizip
GOFILES=multizip.go
DEPS=../../fuse ../../examplelib
DEPS=../../fuse ../../zipfs
include $(GOROOT)/src/Make.cmd
......@@ -2,7 +2,7 @@ package main
import (
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/examplelib"
zipfs "github.com/hanwen/go-fuse/zipfs"
"fmt"
"flag"
"log"
......@@ -20,7 +20,7 @@ func main() {
os.Exit(2)
}
fs := examplelib.NewMultiZipFs()
fs := zipfs.NewMultiZipFs()
state := fuse.NewMountState(fs.Connector)
mountPoint := flag.Arg(0)
......
......@@ -2,8 +2,9 @@
include $(GOROOT)/src/Make.inc
TARG=zipfs
GOFILES=zipfs.go
DEPS=../../fuse ../../examplelib
GOFILES=main.go
DEPS=../../fuse ../../zipfs
include $(GOROOT)/src/Make.cmd
......@@ -2,7 +2,7 @@ package main
import (
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/examplelib"
zipfs "github.com/hanwen/go-fuse/zipfs"
"fmt"
"flag"
"log"
......@@ -21,7 +21,7 @@ func main() {
os.Exit(2)
}
fs := examplelib.NewZipArchiveFileSystem(flag.Arg(1))
fs := zipfs.NewZipArchiveFileSystem(flag.Arg(1))
conn := fuse.NewPathFileSystemConnector(fs)
state := fuse.NewMountState(conn)
......
package examplelib
import (
"github.com/hanwen/go-fuse/fuse"
"sync"
"fmt"
)
var _ = fmt.Println
type subFsInfo struct {
// Entry within global FS.
Name string
Fs fuse.RawFileSystem
// Inode in global namespace.
GlobalNodeId uint64
// Maps Fs's Inodes back to the parent inode.
ParentNodeIds map[uint64]uint64
// This must always be the inner lock in the locking order.
ParentNodeIdsLock sync.RWMutex
Attr fuse.Attr
}
func (me *subFsInfo) getGlobalNode(node uint64) (uint64, bool) {
me.ParentNodeIdsLock.RLock()
defer me.ParentNodeIdsLock.RUnlock()
global, ok := me.ParentNodeIds[node]
return global, ok
}
func (me *subFsInfo) dropGlobalNode(node uint64) {
me.ParentNodeIdsLock.Lock()
defer me.ParentNodeIdsLock.Unlock()
me.ParentNodeIds[node] = 0, false
}
func (me *subFsInfo) addGlobalNode(local uint64, global uint64) {
me.ParentNodeIdsLock.Lock()
defer me.ParentNodeIdsLock.Unlock()
me.ParentNodeIds[local] = global
}
////////////////////////////////////////////////////////////////
type subInodeData struct {
SubFs *subFsInfo
// NodeId in the sub filesystem.
NodeId uint64
LookupCount int
}
func (me *subInodeData) Deletable() bool {
return me.LookupCount <= 0 && (me.NodeId != fuse.FUSE_ROOT_ID || me.SubFs == nil)
}
////////////////////////////////////////////////////////////////
// This is a file system that will composite multiple FUSE
// filesystems, so you can have for example
//
// /dir1 -> one simple FUSE filesystem
// /dir2 -> another simple FUSE filesystem
// /config -> a FS interface to daemon configuration
//
// Each of these directories should be RawFileSystem instances. This
// class takes care of the mapping between global inode ids and inode ids
// of each sub-FUSE filesystem.
//
// No files or directories may be created in the toplevel dir. Instead,
// the daemon should issue AddFileSystem() and RemoveFileSystem()
// methods internally. This could be done in response to writes in a
// /config directory.
type SubmountFileSystem struct {
toplevelEntriesLock sync.RWMutex
toplevelEntries map[string]*subFsInfo
// Mutex protects map and nextFreeInode.
nodeMapLock sync.RWMutex
nodeMap map[uint64]*subInodeData
nextFreeInode uint64
Options SubmountFileSystemOptions
fuse.DefaultRawFuseFileSystem
}
type SubmountFileSystemOptions struct {
fuse.TimeoutOptions
}
////////////////
// Routines that do locking.
func (me *SubmountFileSystem) registerLookup(subInode uint64, subfs *subFsInfo) (globalNodeId uint64) {
globalNodeId, ok := subfs.getGlobalNode(subInode)
var globalNode *subInodeData = nil
me.nodeMapLock.Lock()
defer me.nodeMapLock.Unlock()
if ok {
globalNode = me.nodeMap[globalNodeId]
} else {
globalNodeId = me.nextFreeInode
me.nextFreeInode++
globalNode = &subInodeData{
SubFs: subfs,
NodeId: subInode,
}
me.nodeMap[globalNodeId] = globalNode
subfs.addGlobalNode(subInode, globalNodeId)
}
globalNode.LookupCount++
return globalNodeId
}
func (me *SubmountFileSystem) getSubFs(local uint64) (nodeid uint64, subfs *subFsInfo) {
me.nodeMapLock.RLock()
defer me.nodeMapLock.RUnlock()
data, ok := me.nodeMap[local]
if !ok {
return 0, nil
}
return data.NodeId, data.SubFs
}
func (me *SubmountFileSystem) addFileSystem(name string, fs fuse.RawFileSystem, attr fuse.Attr) bool {
me.toplevelEntriesLock.Lock()
defer me.toplevelEntriesLock.Unlock()
_, ok := me.toplevelEntries[name]
if ok {
return false
}
subfs := &subFsInfo{
Name: name,
Fs: fs,
Attr: attr,
}
me.toplevelEntries[name] = subfs
me.nodeMapLock.Lock()
defer me.nodeMapLock.Unlock()
me.nodeMap[me.nextFreeInode] = &subInodeData{
SubFs: subfs,
NodeId: fuse.FUSE_ROOT_ID,
LookupCount: 0,
}
subfs.ParentNodeIds = map[uint64]uint64{fuse.FUSE_ROOT_ID: me.nextFreeInode}
subfs.GlobalNodeId = me.nextFreeInode
subfs.Attr.Mode |= fuse.S_IFDIR
subfs.Attr.Ino = me.nextFreeInode
me.nextFreeInode++
return true
}
func (me *SubmountFileSystem) removeFileSystem(name string) *subFsInfo {
me.toplevelEntriesLock.Lock()
defer me.toplevelEntriesLock.Unlock()
subfs, ok := me.toplevelEntries[name]
if !ok {
return nil
}
me.toplevelEntries[name] = nil, false
// We leave the keys of node map as is, since the kernel may
// still issue requests with nodeids in it.
me.nodeMapLock.Lock()
defer me.nodeMapLock.Unlock()
for _, v := range me.nodeMap {
if v.SubFs == subfs {
v.SubFs = nil
}
}
return subfs
}
func (me *SubmountFileSystem) listFileSystems() ([]string, []uint32) {
me.toplevelEntriesLock.RLock()
defer me.toplevelEntriesLock.RUnlock()
names := make([]string, len(me.toplevelEntries))
modes := make([]uint32, len(me.toplevelEntries))
j := 0
for name, entry := range me.toplevelEntries {
names[j] = name
modes[j] = entry.Attr.Mode
j++
}
return names, modes
}
func (me *SubmountFileSystem) lookupRoot(name string) (out *fuse.EntryOut, code fuse.Status) {
me.toplevelEntriesLock.RLock()
subfs, ok := me.toplevelEntries[name]
me.toplevelEntriesLock.RUnlock()
if !ok {
out = new(fuse.EntryOut)
out.NodeId = 0
fuse.SplitNs(me.Options.NegativeTimeout, &out.EntryValid, &out.EntryValidNsec)
return nil, fuse.ENOENT
}
me.nodeMapLock.RLock()
dentry, ok := me.nodeMap[subfs.GlobalNodeId]
me.nodeMapLock.RUnlock()
if !ok {
panic(fmt.Sprintf("unknown toplevel node %d", subfs.GlobalNodeId))
}
dentry.LookupCount++
out = new(fuse.EntryOut)
out.NodeId = subfs.GlobalNodeId
out.Attr = subfs.Attr
fuse.SplitNs(me.Options.EntryTimeout, &out.EntryValid, &out.EntryValidNsec)
fuse.SplitNs(me.Options.AttrTimeout, &out.AttrValid, &out.AttrValidNsec)
return out, fuse.OK
}
func (me *SubmountFileSystem) forget(h *fuse.InHeader, input *fuse.ForgetIn) *subInodeData {
me.nodeMapLock.Lock()
defer me.nodeMapLock.Unlock()
subNodeData := me.nodeMap[h.NodeId]
globalNodeId := h.NodeId
subNodeData.LookupCount -= int(input.Nlookup)
if subNodeData.Deletable() {
me.nodeMap[globalNodeId] = nil, false
if subNodeData.SubFs != nil {
subNodeData.SubFs.dropGlobalNode(subNodeData.NodeId)
}
}
return subNodeData
}
////////////////////////////////////////////////////////////////
// Functions below should not need locking primitives.
// Caller should init after this returns successfully.
func (me *SubmountFileSystem) AddFileSystem(name string, fs fuse.RawFileSystem, attr fuse.Attr) bool {
ok := me.addFileSystem(name, fs, attr)
return ok
}
// Caller should call destroy.
func (me *SubmountFileSystem) RemoveFileSystem(name string) fuse.RawFileSystem {
subfs := me.removeFileSystem(name)
if subfs != nil {
return subfs.Fs
}
return nil
}
func (me *SubmountFileSystem) Lookup(h *fuse.InHeader, name string) (out *fuse.EntryOut, code fuse.Status) {
if h.NodeId == fuse.FUSE_ROOT_ID {
return me.lookupRoot(name)
}
subInode, subfs := me.getSubFs(h.NodeId)
if subInode == 0 {
panic("parent unknown")
}
h.NodeId = subInode
out, code = subfs.Fs.Lookup(h, name)
// TODO - is there a named constant for 0 ?
if out == nil || out.NodeId == 0 {
return out, code
}
out.NodeId = me.registerLookup(out.NodeId, subfs)
return out, code
}
func (me *SubmountFileSystem) Forget(h *fuse.InHeader, input *fuse.ForgetIn) {
if h.NodeId != fuse.FUSE_ROOT_ID {
subNodeData := me.forget(h, input)
if subNodeData != nil && subNodeData.SubFs != nil {
h.NodeId = subNodeData.NodeId
subNodeData.SubFs.Fs.Forget(h, input)
}
}
}
func NewSubmountFileSystem() *SubmountFileSystem {
out := new(SubmountFileSystem)
out.nextFreeInode = fuse.FUSE_ROOT_ID + 1
out.nodeMap = make(map[uint64]*subInodeData)
out.toplevelEntries = make(map[string]*subFsInfo)
out.Options.TimeoutOptions = fuse.MakeTimeoutOptions()
return out
}
// What to do about sub fs init's ?
func (me *SubmountFileSystem) Init(h *fuse.InHeader, input *fuse.InitIn) (*fuse.InitOut, fuse.Status) {
return new(fuse.InitOut), fuse.OK
}
func (me *SubmountFileSystem) Destroy(h *fuse.InHeader, input *fuse.InitIn) {
for _, v := range me.toplevelEntries {
v.Fs.Destroy(h, input)
}
}
func (me *SubmountFileSystem) GetAttr(header *fuse.InHeader, input *fuse.GetAttrIn) (out *fuse.AttrOut, code fuse.Status) {
if header.NodeId == fuse.FUSE_ROOT_ID {
out := new(fuse.AttrOut)
// TODO - what to answer for this?
out.Attr.Mode = fuse.S_IFDIR | 0755
return out, fuse.OK
}
subId, subfs := me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
// Looking for attributes of the sub-filesystem mountpoint.
if header.NodeId == subfs.GlobalNodeId {
out := new(fuse.AttrOut)
out.Attr = subfs.Attr
return out, fuse.OK
}
header.NodeId = subId
out, code = subfs.Fs.GetAttr(header, input)
if out != nil {
out.Attr.Ino, _ = subfs.getGlobalNode(out.Ino)
}
return out, code
}
func (me *SubmountFileSystem) Open(header *fuse.InHeader, input *fuse.OpenIn) (flags uint32, fuseFile fuse.RawFuseFile, status fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return 0, nil, fuse.ENOENT
}
return subfs.Fs.Open(header, input)
}
func (me *SubmountFileSystem) SetAttr(header *fuse.InHeader, input *fuse.SetAttrIn) (out *fuse.AttrOut, code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
out, code = subfs.Fs.SetAttr(header, input)
if out != nil {
out.Attr.Ino, _ = subfs.getGlobalNode(out.Ino)
}
return out, code
}
func (me *SubmountFileSystem) Readlink(header *fuse.InHeader) (out []byte, code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
return subfs.Fs.Readlink(header)
}
func (me *SubmountFileSystem) Mknod(header *fuse.InHeader, input *fuse.MknodIn, name string) (out *fuse.EntryOut, code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
out, code = subfs.Fs.Mknod(header, input, name)
if out != nil {
out.NodeId = me.registerLookup(out.NodeId, subfs)
out.Attr.Ino = out.NodeId
}
return out, code
}
func (me *SubmountFileSystem) Mkdir(header *fuse.InHeader, input *fuse.MkdirIn, name string) (out *fuse.EntryOut, code fuse.Status) {
if header.NodeId == fuse.FUSE_ROOT_ID {
// ENOSYS ?
return nil, fuse.EPERM
}
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
out, code = subfs.Fs.Mkdir(header, input, name)
if out != nil {
out.NodeId = me.registerLookup(out.NodeId, subfs)
out.Attr.Ino = out.NodeId
}
return out, code
}
func (me *SubmountFileSystem) Unlink(header *fuse.InHeader, name string) (code fuse.Status) {
if header.NodeId == fuse.FUSE_ROOT_ID {
// ENOSYS ?
return fuse.EPERM
}
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return fuse.ENOENT
}
return subfs.Fs.Unlink(header, name)
}
func (me *SubmountFileSystem) Rmdir(header *fuse.InHeader, name string) (code fuse.Status) {
if header.NodeId == fuse.FUSE_ROOT_ID {
// ENOSYS ?
return fuse.EPERM
}
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return fuse.ENOENT
}
return subfs.Fs.Rmdir(header, name)
}
func (me *SubmountFileSystem) Symlink(header *fuse.InHeader, pointedTo string, linkName string) (out *fuse.EntryOut, code fuse.Status) {
if header.NodeId == fuse.FUSE_ROOT_ID {
// ENOSYS ?
return nil, fuse.EPERM
}
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
out, code = subfs.Fs.Symlink(header, pointedTo, linkName)
if out != nil {
out.NodeId = me.registerLookup(out.NodeId, subfs)
out.Attr.Ino = out.NodeId
}
return out, code
}
func (me *SubmountFileSystem) Rename(header *fuse.InHeader, input *fuse.RenameIn, oldName string, newName string) (code fuse.Status) {
if header.NodeId == fuse.FUSE_ROOT_ID || input.Newdir == fuse.FUSE_ROOT_ID {
// ENOSYS ?
return fuse.EPERM
}
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return fuse.ENOENT
}
return subfs.Fs.Rename(header, input, oldName, newName)
}
func (me *SubmountFileSystem) Link(header *fuse.InHeader, input *fuse.LinkIn, name string) (out *fuse.EntryOut, code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
out, code = subfs.Fs.Link(header, input, name)
if out != nil {
out.NodeId = me.registerLookup(out.NodeId, subfs)
out.Attr.Ino = out.NodeId
}
return out, code
}
func (me *SubmountFileSystem) SetXAttr(header *fuse.InHeader, input *fuse.SetXAttrIn, attr string, data []byte) fuse.Status {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return fuse.ENOENT
}
return subfs.Fs.SetXAttr(header, input, attr, data)
}
func (me *SubmountFileSystem) GetXAttr(header *fuse.InHeader, attr string) (data []byte, code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
return subfs.Fs.GetXAttr(header, attr)
}
func (me *SubmountFileSystem) RemoveXAttr(header *fuse.InHeader, attr string) (code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return fuse.ENOENT
}
return subfs.Fs.RemoveXAttr(header, attr)
}
func (me *SubmountFileSystem) Access(header *fuse.InHeader, input *fuse.AccessIn) (code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
return subfs.Fs.Access(header, input)
}
func (me *SubmountFileSystem) Create(header *fuse.InHeader, input *fuse.CreateIn, name string) (flags uint32, fuseFile fuse.RawFuseFile, out *fuse.EntryOut, code fuse.Status) {
if header.NodeId == fuse.FUSE_ROOT_ID {
// ENOSYS ?
return 0, nil, nil, fuse.EPERM
}
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return 0, nil, nil, fuse.ENOENT
}
flags, fuseFile, out, code = subfs.Fs.Create(header, input, name)
if out != nil {
out.NodeId = me.registerLookup(out.NodeId, subfs)
out.Attr.Ino = out.NodeId
}
return flags, fuseFile, out, code
}
func (me *SubmountFileSystem) Bmap(header *fuse.InHeader, input *fuse.BmapIn) (out *fuse.BmapOut, code fuse.Status) {
var subfs *subFsInfo
if subfs == nil {
return nil, fuse.ENOENT
}
header.NodeId, subfs = me.getSubFs(header.NodeId)
return subfs.Fs.Bmap(header, input)
}
func (me *SubmountFileSystem) Ioctl(header *fuse.InHeader, input *fuse.IoctlIn) (out *fuse.IoctlOut, code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
return subfs.Fs.Ioctl(header, input)
}
func (me *SubmountFileSystem) Poll(header *fuse.InHeader, input *fuse.PollIn) (out *fuse.PollOut, code fuse.Status) {
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return nil, fuse.ENOENT
}
return subfs.Fs.Poll(header, input)
}
func (me *SubmountFileSystem) OpenDir(header *fuse.InHeader, input *fuse.OpenIn) (flags uint32, fuseFile fuse.RawFuseDir, status fuse.Status) {
if header.NodeId == fuse.FUSE_ROOT_ID {
return 0, NewSubmountFileSystemTopDir(me), fuse.OK
}
// TODO - we have to parse and unparse the readdir results, to substitute inodes.
var subfs *subFsInfo
header.NodeId, subfs = me.getSubFs(header.NodeId)
if subfs == nil {
return 0, nil, fuse.ENOENT
}
return subfs.Fs.OpenDir(header, input)
}
func (me *SubmountFileSystem) Release(header *fuse.InHeader, f fuse.RawFuseFile) {
// TODO - should run release on subfs too.
}
func (me *SubmountFileSystem) ReleaseDir(header *fuse.InHeader, f fuse.RawFuseDir) {
// TODO - should run releasedir on subfs too.
}
////////////////////////////////////////////////////////////////
type SubmountFileSystemTopDir struct {
names []string
modes []uint32
nextRead int
fuse.DefaultRawFuseDir
}
func NewSubmountFileSystemTopDir(fs *SubmountFileSystem) *SubmountFileSystemTopDir {
out := new(SubmountFileSystemTopDir)
out.names, out.modes = fs.listFileSystems()
return out
}
func (me *SubmountFileSystemTopDir) ReadDir(input *fuse.ReadIn) (*fuse.DirEntryList, fuse.Status) {
de := fuse.NewDirEntryList(int(input.Size))
for me.nextRead < len(me.names) {
i := me.nextRead
if de.AddString(me.names[i], fuse.FUSE_UNKNOWN_INO, me.modes[i]) {
me.nextRead++
} else {
break
}
}
return de, fuse.OK
}
package examplelib
import (
"github.com/hanwen/go-fuse/fuse"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"testing"
)
var _ = strings.Join
var _ = log.Println
////////////////
// Create and mount filesystem.
const magicMode uint32 = 0753
type stackFsTestCase struct {
origDir1 string
origDir2 string
mountDir string
testDir string
tester *testing.T
fs *SubmountFileSystem
state *fuse.MountState
}
func (me *stackFsTestCase) Setup(t *testing.T) {
me.tester = t
me.testDir = fuse.MakeTempDir()
me.origDir1 = filepath.Join(me.testDir, "orig1")
me.origDir2 = filepath.Join(me.testDir, "orig2")
me.mountDir = filepath.Join(me.testDir, "mount")
os.Mkdir(me.origDir1, 0700)
os.Mkdir(me.origDir2, 0700)
os.Mkdir(me.mountDir, 0700)
fs1 := fuse.NewPathFileSystemConnector(fuse.NewLoopbackFileSystem(me.origDir1))
fs2 := fuse.NewPathFileSystemConnector(fuse.NewLoopbackFileSystem(me.origDir2))
me.fs = NewSubmountFileSystem()
attr := fuse.Attr{
Mode: uint32(magicMode),
}
me.fs.AddFileSystem("sub1", fs1, attr)
me.fs.AddFileSystem("sub2", fs2, attr)
me.state = fuse.NewMountState(me.fs)
me.state.Mount(me.mountDir)
me.state.Debug = true
fmt.Println("tempdir: ", me.testDir)
// Unthreaded, but in background.
go me.state.Loop(false)
}
// Unmount and del.
func (me *stackFsTestCase) Cleanup() {
fmt.Println("Unmounting.")
err := me.state.Unmount()
CheckSuccess(err)
os.RemoveAll(me.testDir)
}
////////////////
func (me *stackFsTestCase) testReaddir() {
fmt.Println("testReaddir... ")
dir, err := os.Open(me.mountDir)
CheckSuccess(err)
infos, err := dir.Readdir(10)
CheckSuccess(err)
wanted := map[string]bool{
"sub1": true,
"sub2": true,
}
if len(wanted) != len(infos) {
me.tester.Errorf("Length mismatch %v", infos)
} else {
for _, v := range infos {
_, ok := wanted[v.Name]
if !ok {
me.tester.Errorf("Unexpected name %v", v.Name)
}
if v.Mode&0777 != magicMode {
me.tester.Errorf("Unexpected mode %o, %v", v.Mode, v)
}
}
}
dir.Close()
}
func (me *stackFsTestCase) testSubFs() {
fmt.Println("testSubFs... ")
for i := 1; i <= 2; i++ {
// orig := filepath.Join(me.testDir, fmt.Sprintf("orig%d", i))
mount := filepath.Join(me.mountDir, fmt.Sprintf("sub%d", i))
name := "testFile"
mountFile := filepath.Join(mount, name)
f, err := os.OpenFile(mountFile, os.O_WRONLY, 0)
if err == nil {
me.tester.Errorf("Expected error for open write %v", name)
continue
}
content1 := "booh!"
f, err = os.Create(mountFile)
CheckSuccess(err)
f.Write([]byte(content1))
f.Close()
err = os.Chmod(mountFile, magicMode)
CheckSuccess(err)
fi, err := os.Lstat(mountFile)
CheckSuccess(err)
if fi.Mode&0777 != magicMode {
me.tester.Errorf("Mode %o", fi.Mode)
}
g, err := os.Open(mountFile)
CheckSuccess(err)
buf := make([]byte, 1024)
n, err := g.Read(buf)
CheckSuccess(err)
if string(buf[:n]) != content1 {
me.tester.Errorf("content %v", buf[:n])
}
g.Close()
}
}
func (me *stackFsTestCase) testAddRemove() {
me.tester.Log("testAddRemove")
attr := fuse.Attr{
Mode: 0755,
}
conn := fuse.NewPathFileSystemConnector(fuse.NewLoopbackFileSystem(me.origDir1))
ok := me.fs.AddFileSystem("sub1", conn, attr)
if ok {
me.tester.Errorf("AddFileSystem should fail")
return
}
ok = me.fs.AddFileSystem("third", conn, attr)
if !ok {
me.tester.Errorf("AddFileSystem fail")
}
conn.Init(new(fuse.InHeader), new(fuse.InitIn))
fi, err := os.Lstat(filepath.Join(me.mountDir, "third"))
CheckSuccess(err)
if !fi.IsDirectory() {
me.tester.Errorf("not a directory %v", fi)
}
fs := me.fs.RemoveFileSystem("third")
if fs == nil {
me.tester.Errorf("remove fail")
}
dir, err := os.Open(me.mountDir)
CheckSuccess(err)
infos, err := dir.Readdir(10)
CheckSuccess(err)
if len(infos) != 2 {
me.tester.Errorf("lstat expect 2 infos %v", infos)
}
dir.Close()
_, err = os.Open(filepath.Join(me.mountDir, "third"))
if err == nil {
me.tester.Errorf("expect enoent %v", err)
}
}
func TestStackFS(t *testing.T) {
ts := new(stackFsTestCase)
ts.Setup(t)
ts.testReaddir()
ts.testSubFs()
ts.testAddRemove()
ts.Cleanup()
}
......@@ -2,7 +2,6 @@
include $(GOROOT)/src/Make.inc
TARG=github.com/hanwen/go-fuse/fuse
#TARG=fuse
GOFILES=misc.go\
fuse.go\
......
......@@ -60,7 +60,7 @@ func (me *AutoUnionFs) addFs(roots []string) {
var gofs *UnionFs
if me.knownFilesystems[name] == nil {
log.Println("Adding UnionFs for roots", roots)
gofs = NewUnionfs(roots, me.options.UnionFsOptions)
gofs = NewUnionFs(roots, me.options.UnionFsOptions)
me.knownFilesystems[name] = gofs
}
me.lock.Unlock()
......
......@@ -78,7 +78,7 @@ type UnionFsOptions struct {
DeletionDirName string
}
func NewUnionfs(roots []string, options UnionFsOptions) *UnionFs {
func NewUnionFs(roots []string, options UnionFsOptions) *UnionFs {
g := new(UnionFs)
g.options = &options
......
......@@ -38,7 +38,7 @@ func setup(t *testing.T) (workdir string, state *fuse.MountState) {
var roots []string
roots = append(roots, wd+"/rw")
roots = append(roots, wd+"/ro")
ufs := NewUnionfs(roots, testOpts)
ufs := NewUnionFs(roots, testOpts)
connector := fuse.NewPathFileSystemConnector(ufs)
state = fuse.NewMountState(connector)
......
# Use "gomake install" to build and install this package.
include $(GOROOT)/src/Make.inc
TARG=github.com/hanwen/go-fuse/zipfs
DEPS=../fuse
GOFILES=zipfs.go \
multizip.go \
include $(GOROOT)/src/Make.pkg
......@@ -2,11 +2,19 @@ package examplelib
import (
"github.com/hanwen/go-fuse/fuse"
"log"
"os"
"testing"
"time"
)
func CheckSuccess(err os.Error) {
if err != nil {
log.Println(err)
panic("error")
}
}
func TestMultiZipFs(t *testing.T) {
var err os.Error
......@@ -48,7 +56,9 @@ func TestMultiZipFs(t *testing.T) {
CheckSuccess(err)
// Directory exists, but is empty.
if !IsDir(mountPoint + "/zipmount") {
fi, err := os.Lstat(mountPoint + "/zipmount")
CheckSuccess(err)
if !fi.IsDirectory() {
t.Errorf("Expect directory at /zipmount")
}
......@@ -63,13 +73,13 @@ func TestMultiZipFs(t *testing.T) {
err = f.Close()
CheckSuccess(err)
if !IsDir(mountPoint + "/zipmount") {
fi, err = os.Lstat(mountPoint + "/zipmount")
if !fi.IsDirectory() {
t.Errorf("Expect directory at /zipmount")
}
// Check that zipfs itself works.
fi, err := os.Stat(mountPoint + "/zipmount/subdir")
fi, err = os.Stat(mountPoint + "/zipmount/subdir")
CheckSuccess(err)
if !fi.IsDirectory() {
t.Error("directory type", fi)
......
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