Commit c0f14a4a authored by Kirill Smelkov's avatar Kirill Smelkov

Merge remote-tracking branch 'origin/master' into t

* origin/master:
  fs: remove Inode.Parents
  Update top-level documentation (README and doc.go)
  fs: split zipfs into a example file and a test file
  fs: example for handleless read/write
  fs: rename Options.DefaultPermissions to NullPermissions
  fs: add an example for file handles with direct I/O
  fuse/test: make StatFs tests nonverbose
  internal/testutil: switch off debug if test is called TestNonVerbose
  fs: return OK for unimplemented Flush
  fs: add doc capturing design decisions
  fs: fix doc nits
  fs: add more package docs.
parents 1e564b6a 4dd68784
......@@ -3,26 +3,30 @@
[![Build Status](https://travis-ci.org/hanwen/go-fuse.svg?branch=master)](https://travis-ci.org/hanwen/go-fuse)
[![GoDoc](https://godoc.org/github.com/hanwen/go-fuse?status.svg)](https://godoc.org/github.com/hanwen/go-fuse)
native bindings for the FUSE kernel module.
Go native bindings for the FUSE kernel module.
## Highlights
You should import and use
[github.com/hanwen/go-fuse/fs](https://godoc.org/github.com/hanwen/go-fuse/fs)
library. It follows the wire protocol closely, but provides
convenient abstractions for building both node and path based file
systems
* High speed: as fast as libfuse using the gc compiler for single
threaded loads.
Older, deprecated APIs are available at
[github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/fuse/pathfs)
and
[github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/fuse/nodefs).
* Supports in-process mounting of different FileSystems onto
subdirectories of the FUSE mount.
## Comparison with other FUSE libraries
* Supports 3 interfaces for writing filesystems:
- `PathFileSystem`: define filesystems in terms path names.
- `NodeFileSystem`: define filesystems in terms of inodes.
- `RawFileSystem`: define filesystems in terms of FUSE's raw
wire protocol.
The FUSE library gained a new, cleaned-up API during a rewrite
completed in 2019. Find extensive documentation
[here](https://godoc.org/github.com/hanwen/go-fuse/).
* Both NodeFileSystem and PathFileSystem support manipulation of true
hardlinks.
Further highlights of this library is
* Includes two fleshed out examples, zipfs and unionfs.
* Comprehensive and up to date protocol support (up to 7.12.28).
* Performance that is competitive with libfuse.
## Examples
......@@ -54,50 +58,6 @@ subdirectories of the FUSE mount.
fusermount -u /tmp/mountpoint
```
* `unionfs/unionfs.go`: implements a union mount using 1 R/W branch, and
multiple R/O branches.
```shell
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).
- x86 64bits (Ubuntu Lucid).
## Benchmarks
We use threaded stats over a read-only filesystem for benchmarking.
Automated code is under benchmark/ directory. A simple C version of
the same FS gives a FUSE baseline
Data points (Go-FUSE version May 2012), 1000 files, high level
interface, all kernel caching turned off, median stat time:
platform libfuse Go-FUSE difference (%)
Lenovo T60/Fedora16 (1cpu) 349us 355us 2% slower
Lenovo T400/Lucid (1cpu) 138us 140us 5% slower
Dell T3500/Lucid (1cpu) 72us 76us 5% slower
On T60, for each file we have
- Client side latency is 360us
- 106us of this is server side latency (4.5x lookup 23us, 1x getattr 4us)
- 16.5us is due to latency measurements.
- 3us is due to garbage collection.
## macOS Support
go-fuse works somewhat on OSX. Known limitations:
......@@ -134,12 +94,6 @@ Grep source code for TODO. Major topics:
* Missing support for `CUSE`, `BMAP`, `IOCTL`
* In the path API, renames are racy; See also:
http://sourceforge.net/mailarchive/message.php?msg_id=27550667
Don't use the path API if you care about correctness.
## License
Like Go, this library is distributed under the new BSD license. See
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This is a repository containing Go bindings for writing FUSE file
// systems.
//
// Go to https://godoc.org/github.com/hanwen/go-fuse/fs for the
// in-depth documentation for this library.
//
// Older, deprecated APIs are available at
// https://godoc.org/github.com/hanwen/go-fuse/fuse/pathfs and
// https://godoc.org/github.com/hanwen/go-fuse/fuse/nodefs.
package lib
......@@ -34,7 +34,6 @@ func main() {
opts := fs.Options{
EntryTimeout: &sec,
AttrTimeout: &sec,
DefaultPermissions: true,
}
opts.Debug = *debug
server, err := fs.Mount(flag.Arg(0), root, &opts)
......
......@@ -59,7 +59,6 @@ func main() {
opts := &fs.Options{
AttrTimeout: ttl,
EntryTimeout: ttl,
DefaultPermissions: true,
}
opts.Debug = *debug
server, err := fs.Mount(flag.Arg(0), root, opts)
......
Objective
=========
A high-performance FUSE API that minimizes pitfalls with writing
correct filesystems.
Decisions
=========
* Nodes contain references to their children. This is useful
because most filesystems will need to construct tree-like
structures.
* Nodes contain references to their parents. As a result, we can
derive the path for each Inode, and there is no need for a
separate PathFS.
* Nodes can be "persistent", meaning their lifetime is not under
control of the kernel. This is useful for constructing FS trees
in advance, rather than driven by LOOKUP.
* The NodeID for FS tree node must be defined on creation and are
immutable. By contrast, reusing NodeIds (eg. rsc/bazil FUSE, as
well as old go-fuse/fuse/nodefs) needs extra synchronization to
avoid races with notify and FORGET, and makes handling the inode
Generation more complicated.
* The mode of an Inode is defined on creation. Files cannot change
type during their lifetime. This also prevents the common error
of forgetting to return the filetype in Lookup/GetAttr.
* The NodeID (used for communicating with kernel) is equal to
Attr.Ino (value shown in Stat and Lstat return values.).
* No global treelock, to ensure scalability.
* Support for hard links. libfuse doesn't support this in the
high-level API. Extra care for race conditions is needed when
looking up the same file through different paths.
* do not issue Notify{Entry,Delete} as part of
AddChild/RmChild/MvChild: because NodeIDs are unique and
immutable, there is no confusion about which nodes are
invalidated, and the notification doesn't have to happen under
lock.
* Directory reading uses the DirStream. Semantics for rewinding
directory reads, and adding files after opening (but before
reading) are handled automatically. No support for directory
seeks.
* Method names are based on syscall names. Where there is no
syscall (eg. "open directory"), we bias towards writing
everything together (Opendir)
To do/To decide
=========
* Symlink []byte vs string.
This diff is collapsed.
......@@ -147,7 +147,7 @@ func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) {
}
func (b *rawBridge) setAttr(out *fuse.Attr) {
if b.options.DefaultPermissions && out.Mode&07777 == 0 {
if !b.options.NullPermissions && out.Mode&07777 == 0 {
out.Mode |= 0644
if out.Mode&syscall.S_IFDIR != 0 {
out.Mode |= 0111
......@@ -743,8 +743,7 @@ func (b *rawBridge) Flush(cancel <-chan struct{}, input *fuse.FlushIn) fuse.Stat
if fl, ok := f.file.(FileFlusher); ok {
return errnoToStatus(fl.Flush(&fuse.Context{Caller: input.Caller, Cancel: cancel}))
}
// XXX should return OK to reflect r/o filesystem?
return fuse.ENOTSUP
return 0
}
func (b *rawBridge) Fsync(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status {
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs_test
import (
"context"
"fmt"
"log"
"syscall"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
)
// bytesFileHandle is a file handle that carries separate content for
// each Open call
type bytesFileHandle struct {
content []byte
}
// bytesFileHandle allows reads
var _ = (fs.FileReader)((*bytesFileHandle)(nil))
func (fh *bytesFileHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := off + int64(len(dest))
if end > int64(len(fh.content)) {
end = int64(len(fh.content))
}
// We could copy to the `dest` buffer, but since we have a
// []byte already, return that.
return fuse.ReadResultData(fh.content[off:end]), 0
}
// timeFile is a file that contains the wall clock time as ASCII.
type timeFile struct {
fs.Inode
}
// timeFile implements Open
var _ = (fs.NodeOpener)((*timeFile)(nil))
func (f *timeFile) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
// disallow writes
if fuseFlags&(syscall.O_RDWR|syscall.O_WRONLY) != 0 {
return nil, 0, syscall.EROFS
}
// capture open time
now := time.Now().Format(time.StampNano) + "\n"
fh = &bytesFileHandle{
content: []byte(now),
}
// Return FOPEN_DIRECT_IO so content is not cached.
return fh, fuse.FOPEN_DIRECT_IO, 0
}
// ExampleDirectIO shows how to create a file whose contents change on
// every read.
func Example_directIO() {
mntDir := "/tmp/x"
root := &fs.Inode{}
// Mount the file system
server, err := fs.Mount(mntDir, root, &fs.Options{
MountOptions: fuse.MountOptions{Debug: false},
// Setup the clock file.
OnAdd: func(ctx context.Context) {
ch := root.NewPersistentInode(
ctx,
&timeFile{},
fs.StableAttr{Mode: syscall.S_IFREG})
root.AddChild("clock", ch, true)
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("cat %s/clock to see the time\n", mntDir)
fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir)
// Serve the file system, until unmounted by calling fusermount -u
server.Wait()
}
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs_test
import (
"context"
"log"
"os"
"strconv"
"syscall"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
)
// numberNode is a filesystem node representing an integer. Prime
// numbers are regular files, while composite numbers are directories
// containing all smaller numbers, eg.
//
// $ ls -F /tmp/x/6
// 2 3 4/ 5
//
// the file system nodes are deduplicated using inode numbers. The
// number 2 appears in many directories, but it is actually the represented
// by the same numberNode{} object, with inode number 2.
//
// $ ls -i1 /tmp/x/2 /tmp/x/8/6/4/2
// 2 /tmp/x/2
// 2 /tmp/x/8/6/4/2
//
type numberNode struct {
// Must embed an Inode for the struct to work as a node.
fs.Inode
// num is the integer represented in this file/directory
num int
}
// isPrime returns whether n is prime
func isPrime(n int) bool {
for i := 2; i*i <= n; i++ {
if n%i == 0 {
return false
}
}
return true
}
func numberToMode(n int) uint32 {
// prime numbers are files
if isPrime(n) {
return fuse.S_IFREG
}
// composite numbers are directories
return fuse.S_IFDIR
}
// Ensure we are implementing the NodeReaddirer interface
var _ = (fs.NodeReaddirer)((*numberNode)(nil))
// Readdir is part of the NodeReaddirer interface
func (n *numberNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
r := make([]fuse.DirEntry, 0, n.num)
for i := 2; i < n.num; i++ {
d := fuse.DirEntry{
Name: strconv.Itoa(i),
Ino: uint64(i),
Mode: numberToMode(i),
}
r = append(r, d)
}
return fs.NewListDirStream(r), 0
}
// Ensure we are implementing the NodeLookuper interface
var _ = (fs.NodeLookuper)((*numberNode)(nil))
// Lookup is part of the NodeLookuper interface
func (n *numberNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
i, err := strconv.Atoi(name)
if err != nil {
return nil, syscall.ENOENT
}
if i >= n.num || i <= 1 {
return nil, syscall.ENOENT
}
stable := fs.StableAttr{
Mode: numberToMode(i),
// The child inode is identified by its Inode number.
// If multiple concurrent lookups try to find the same
// inode, they are deduplicated on this key.
Ino: uint64(i),
}
operations := &numberNode{num: i}
// The NewInode call wraps the `operations` object into an Inode.
child := n.NewInode(ctx, operations, stable)
// In case of concurrent lookup requests, it can happen that operations !=
// child.Operations().
return child, 0
}
// ExampleDynamic is a whimsical example of a dynamically discovered
// file system.
func Example_dynamic() {
// This is where we'll mount the FS
mntDir := "/tmp/x"
os.Mkdir(mntDir, 0755)
root := &numberNode{num: 10}
server, err := fs.Mount(mntDir, root, &fs.Options{
MountOptions: fuse.MountOptions{
// Set to true to see how the file system works.
Debug: true,
},
})
if err != nil {
log.Panic(err)
}
log.Printf("Mounted on %s", mntDir)
log.Printf("Unmount by calling 'fusermount -u %s'", mntDir)
// Wait until unmount before exiting
server.Wait()
}
......@@ -14,37 +14,30 @@ import (
"github.com/hanwen/go-fuse/fuse"
)
// mountLoopback mounts dir under the given mountpoint
func mountLoopback(dir, mntPoint string) (*fuse.Server, error) {
root, err := fs.NewLoopbackRoot(dir)
// ExampleMount shows how to create a loopback file system, and
// mounting it onto a directory
func Example_mount() {
mntDir, _ := ioutil.TempDir("", "")
home := os.Getenv("HOME")
// Make $HOME available on a mount dir under /tmp/ . Caution:
// write operations are also mirrored.
root, err := fs.NewLoopbackRoot(home)
if err != nil {
return nil, err
log.Fatal(err)
}
// Make the root available under mntDir
return fs.Mount(mntPoint, root, &fs.Options{
// Mount the file system
server, err := fs.Mount(mntDir, root, &fs.Options{
MountOptions: fuse.MountOptions{Debug: true},
})
}
// An example of creating a loopback file system, and mounting it onto
// a directory
func Example_mountLoopback() {
mntDir, _ := ioutil.TempDir("", "")
home := os.Getenv("HOME")
// Make $HOME available on a mount dir under /tmp/ . Caution:
// write operations are also mirrored.
server, err := mountLoopback(mntDir, home)
if err != nil {
log.Panic(err)
log.Fatal(err)
}
fmt.Printf("Mounted %s as loopback on %s\n", home, mntDir)
fmt.Printf("\n\nCAUTION:\nwrite operations on %s will also affect $HOME (%s)\n\n", mntDir, home)
fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir)
// Wait until the directory is unmounted
// Serve the file system, until unmounted by calling fusermount -u
server.Wait()
}
......@@ -80,11 +80,6 @@ func Example() {
root := &inMemoryFS{}
server, err := fs.Mount(mntDir, root, &fs.Options{
MountOptions: fuse.MountOptions{Debug: true},
// This adds read permissions to the files and
// directories, which is necessary for doing a chdir
// into the mount.
DefaultPermissions: true,
})
if err != nil {
log.Panic(err)
......
......@@ -316,6 +316,15 @@ func (iparent *Inode) setEntry(name string, ichild *Inode) {
// NewPersistentInode returns an Inode whose lifetime is not in
// control of the kernel.
//
// When the kernel is short on memory, it will forget cached file
// system information (directory entries and inode metadata). This is
// announced with FORGET messages. There are no guarantees if or when
// this happens. When it happens, these are handled transparently by
// go-fuse: all Inodes created with NewInode are released
// automatically. NewPersistentInode creates inodes that go-fuse keeps
// in memory, even if the kernel is not interested in them. This is
// convenient for building static trees up-front.
func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode {
return n.newInode(ctx, node, id, true)
}
......@@ -474,18 +483,6 @@ func (n *Inode) Children() map[string]*Inode {
return r
}
// Parents returns the parents of this Inode, along with the name
// with which they're are a child
func (n *Inode) Parents() map[string]*Inode {
n.mu.Lock()
defer n.mu.Unlock()
r := make(map[string]*Inode, len(n.parents))
for k := range n.parents {
r[k.name] = k.parent
}
return r
}
// Parents returns a parent of this Inode, or nil if this Inode is
// deleted or is the root
func (n *Inode) Parent() (string, *Inode) {
......
......@@ -29,7 +29,6 @@ func TestDefaultPermissions(t *testing.T) {
root := &Inode{}
mntDir, _, clean := testMount(t, root, &Options{
DefaultPermissions: true,
OnAdd: func(ctx context.Context) {
dir := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFDIR})
file := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFREG})
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs_test
import (
"context"
"fmt"
"log"
"sync"
"syscall"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
)
// bytesNode is a file that can be read and written
type bytesNode struct {
fs.Inode
// When file systems are mutable, all access must use
// synchronization.
mu sync.Mutex
content []byte
mtime time.Time
}
// Implement GetAttr to provide size and mtime
var _ = (fs.NodeGetattrer)((*bytesNode)(nil))
func (bn *bytesNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
bn.mu.Lock()
defer bn.mu.Unlock()
bn.getattr(out)
return 0
}
func (bn *bytesNode) getattr(out *fuse.AttrOut) {
out.Size = uint64(len(bn.content))
out.SetTimes(nil, &bn.mtime, nil)
}
func (bn *bytesNode) resize(sz uint64) {
if sz > uint64(cap(bn.content)) {
n := make([]byte, sz)
copy(n, bn.content)
bn.content = n
} else {
bn.content = bn.content[:sz]
}
bn.mtime = time.Now()
}
// Implement Setattr to support truncation
var _ = (fs.NodeSetattrer)((*bytesNode)(nil))
func (bn *bytesNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
bn.mu.Lock()
defer bn.mu.Unlock()
if sz, ok := in.GetSize(); ok {
bn.resize(sz)
}
bn.getattr(out)
return 0
}
// Implement handleless read.
var _ = (fs.NodeReader)((*bytesNode)(nil))
func (bn *bytesNode) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
bn.mu.Lock()
defer bn.mu.Unlock()
end := off + int64(len(dest))
if end > int64(len(bn.content)) {
end = int64(len(bn.content))
}
// We could copy to the `dest` buffer, but since we have a
// []byte already, return that.
return fuse.ReadResultData(bn.content[off:end]), 0
}
// Implement handleless write.
var _ = (fs.NodeWriter)((*bytesNode)(nil))
func (bn *bytesNode) Write(ctx context.Context, fh fs.FileHandle, buf []byte, off int64) (uint32, syscall.Errno) {
bn.mu.Lock()
defer bn.mu.Unlock()
sz := int64(len(buf))
if off+sz > int64(len(bn.content)) {
bn.resize(uint64(off + sz))
}
copy(bn.content[off:], buf)
bn.mtime = time.Now()
return uint32(sz), 0
}
// Implement (handleless) Open
var _ = (fs.NodeOpener)((*bytesNode)(nil))
func (f *bytesNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
return nil, 0, 0
}
// ExampleHandleLess shows how to create a file that can be read or written
// by implementing Read/Write directly on the nodes.
func Example_handleLess() {
mntDir := "/tmp/x"
root := &fs.Inode{}
// Mount the file system
server, err := fs.Mount(mntDir, root, &fs.Options{
MountOptions: fuse.MountOptions{Debug: false},
// Setup the file.
OnAdd: func(ctx context.Context) {
ch := root.NewPersistentInode(
ctx,
&bytesNode{},
fs.StableAttr{
Mode: syscall.S_IFREG,
// Make debug output readable.
Ino: 2,
})
root.AddChild("bytes", ch, true)
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf(`Try:
cd %s
ls -l bytes
echo hello > bytes
ls -l bytes
cat bytes
cd -
`, mntDir)
fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir)
// Serve the file system, until unmounted by calling fusermount -u
server.Wait()
}
......@@ -11,13 +11,10 @@ import (
"io/ioutil"
"path/filepath"
"reflect"
"strings"
"sync"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
)
var testData = map[string]string{
......@@ -134,91 +131,3 @@ func TestZipFSOnAdd(t *testing.T) {
t.Errorf("got %q, want %q", got, want)
}
}
// zipFile is a file read from a zip archive.
type zipFile struct {
fs.Inode
file *zip.File
mu sync.Mutex
data []byte
}
var _ = (fs.NodeOpener)((*zipFile)(nil))
var _ = (fs.NodeGetattrer)((*zipFile)(nil))
// Getattr sets the minimum, which is the size. A more full-featured
// FS would also set timestamps and permissions.
func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Size = zf.file.UncompressedSize64
return 0
}
// Open lazily unpacks zip data
func (zf *zipFile) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
zf.mu.Lock()
defer zf.mu.Unlock()
if zf.data == nil {
rc, err := zf.file.Open()
if err != nil {
return nil, 0, syscall.EIO
}
content, err := ioutil.ReadAll(rc)
if err != nil {
return nil, 0, syscall.EIO
}
zf.data = content
}
// We don't return a filehandle since we don't really need
// one. The file content is immutable, so hint the kernel to
// cache the data.
return nil, fuse.FOPEN_KEEP_CACHE, fs.OK
}
// Read simply returns the data that was already unpacked in the Open call
func (zf *zipFile) Read(ctx context.Context, f fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := int(off) + len(dest)
if end > len(zf.data) {
end = len(zf.data)
}
return fuse.ReadResultData(zf.data[off:end]), fs.OK
}
// zipRoot is the root of the Zip filesystem. Its only functionality
// is populating the filesystem.
type zipRoot struct {
fs.Inode
zr *zip.Reader
}
var _ = (fs.NodeOnAdder)((*zipRoot)(nil))
func (zr *zipRoot) OnAdd(ctx context.Context) {
// OnAdd is called once we are attached to an Inode. We can
// then construct a tree. We construct the entire tree, and
// we don't want parts of the tree to disappear when the
// kernel is short on memory, so we use persistent inodes.
for _, f := range zr.zr.File {
dir, base := filepath.Split(f.Name)
p := &zr.Inode
for _, component := range strings.Split(dir, "/") {
if len(component) == 0 {
continue
}
ch := p.GetChild(component)
if ch == nil {
ch = p.NewPersistentInode(ctx, &fs.Inode{},
fs.StableAttr{Mode: fuse.S_IFDIR})
p.AddChild(component, ch, true)
}
p = ch
}
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, fs.StableAttr{})
p.AddChild(base, ch, true)
}
}
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs_test
import (
"archive/zip"
"context"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
)
// zipFile is a file read from a zip archive.
type zipFile struct {
fs.Inode
file *zip.File
mu sync.Mutex
data []byte
}
// We decompress the file on demand in Open
var _ = (fs.NodeOpener)((*zipFile)(nil))
// Getattr sets the minimum, which is the size. A more full-featured
// FS would also set timestamps and permissions.
var _ = (fs.NodeGetattrer)((*zipFile)(nil))
func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Size = zf.file.UncompressedSize64
return 0
}
// Open lazily unpacks zip data
func (zf *zipFile) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
zf.mu.Lock()
defer zf.mu.Unlock()
if zf.data == nil {
rc, err := zf.file.Open()
if err != nil {
return nil, 0, syscall.EIO
}
content, err := ioutil.ReadAll(rc)
if err != nil {
return nil, 0, syscall.EIO
}
zf.data = content
}
// We don't return a filehandle since we don't really need
// one. The file content is immutable, so hint the kernel to
// cache the data.
return nil, fuse.FOPEN_KEEP_CACHE, fs.OK
}
// Read simply returns the data that was already unpacked in the Open call
func (zf *zipFile) Read(ctx context.Context, f fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := int(off) + len(dest)
if end > len(zf.data) {
end = len(zf.data)
}
return fuse.ReadResultData(zf.data[off:end]), fs.OK
}
// zipRoot is the root of the Zip filesystem. Its only functionality
// is populating the filesystem.
type zipRoot struct {
fs.Inode
zr *zip.Reader
}
// The root populates the tree in its OnAdd method
var _ = (fs.NodeOnAdder)((*zipRoot)(nil))
func (zr *zipRoot) OnAdd(ctx context.Context) {
// OnAdd is called once we are attached to an Inode. We can
// then construct a tree. We construct the entire tree, and
// we don't want parts of the tree to disappear when the
// kernel is short on memory, so we use persistent inodes.
for _, f := range zr.zr.File {
dir, base := filepath.Split(f.Name)
p := &zr.Inode
for _, component := range strings.Split(dir, "/") {
if len(component) == 0 {
continue
}
ch := p.GetChild(component)
if ch == nil {
ch = p.NewPersistentInode(ctx, &fs.Inode{},
fs.StableAttr{Mode: fuse.S_IFDIR})
p.AddChild(component, ch, true)
}
p = ch
}
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, fs.StableAttr{})
p.AddChild(base, ch, true)
}
}
// ExampleZipFS shows an in-memory, static file system
func Example_zipFS() {
flag.Parse()
if len(flag.Args()) != 1 {
log.Fatal("usage: zipmount ZIP-FILE")
}
zfile, err := zip.OpenReader(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
root := &zipRoot{zr: &zfile.Reader}
mnt := "/tmp/x"
os.Mkdir(mnt, 0755)
server, err := fs.Mount(mnt, root, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("zip file mounted")
fmt.Printf("to unmount: fusermount -u %s\n", mnt)
server.Wait()
}
......@@ -853,7 +853,7 @@ func TestIoctl(t *testing.T) {
// This test is racy. If an external process consumes space while this
// runs, we may see spurious differences between the two statfs() calls.
func TestStatFs(t *testing.T) {
func TestNonVerboseStatFs(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
......@@ -875,7 +875,7 @@ func TestStatFs(t *testing.T) {
}
}
func TestFStatFs(t *testing.T) {
func TestNonVerboseFStatFs(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
......
......@@ -4,10 +4,20 @@
package testutil
import "flag"
import (
"bytes"
"flag"
"runtime"
)
// VerboseTest returns true if the testing framework is run with -v.
func VerboseTest() bool {
var buf [2048]byte
n := runtime.Stack(buf[:], false)
if bytes.Index(buf[:n], []byte("TestNonVerbose")) != -1 {
return false
}
flag := flag.Lookup("test.v")
return flag != nil && flag.Value.String() == "true"
}
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