Commit 56dcd64b authored by Kirill Smelkov's avatar Kirill Smelkov

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

* origin/master: (28 commits)
  nodefs: use offset for MemRegularFile reads. Test it.
  zipfs: fix and test tarfs
  nodefs: don't tweak MemRegularFile mode
  nodefs: fix builds on darwin
  example/hello: convert hello to new nodefs API
  nodefs: add MemRegularFile.Flush
  example/statfs: use new nodefs library
  example/loopback: use new nodefs library
  nodefs: document Access
  example/zipfs: use new nodefs
  nodefs: use bridge.getattr for Access calculation
  example/multizip: use new nodefs package.
  nodefs: comment ExampleNewPersistentInode
  nodefs: add Options.DefaultPermissions
  zipfs: rewrite using new nodefs API.
  nodefs: use testMount in tests
  nodefs: don't pass root into Options.OnAdd
  nodefs: add Inode.RmAllChildren
  nodefs: various fixes
  nodefs: check that default Create returns EROFS
  ...
parents a510f37f 928afa1c
......@@ -13,32 +13,27 @@ import (
"path/filepath"
"runtime"
"sort"
"syscall"
"testing"
"time"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/nodefs"
)
func setupFs(fs pathfs.FileSystem, N int) (string, func()) {
opts := &nodefs.Options{
EntryTimeout: 0.0,
AttrTimeout: 0.0,
NegativeTimeout: 0.0,
}
func setupFs(fs nodefs.InodeEmbedder, N int) (string, func()) {
opts := &nodefs.Options{}
opts.Debug = testutil.VerboseTest()
mountPoint := testutil.TempDir()
nfs := pathfs.NewPathNodeFs(fs, nil)
state, _, err := nodefs.MountRoot(mountPoint, nfs.Root(), opts)
server, err := nodefs.Mount(mountPoint, fs, opts)
if err != nil {
panic(fmt.Sprintf("cannot mount %v", err)) // ugh - benchmark has no error methods.
log.Panicf("cannot mount %v", err)
}
lmap := NewLatencyMap()
if testutil.VerboseTest() {
state.RecordLatencies(lmap)
server.RecordLatencies(lmap)
}
go state.Serve()
return mountPoint, func() {
if testutil.VerboseTest() {
var total time.Duration
......@@ -55,7 +50,7 @@ func setupFs(fs pathfs.FileSystem, N int) (string, func()) {
log.Printf("total %v, %v/bench op", total, total/time.Duration(N))
}
err := state.Unmount()
err := server.Unmount()
if err != nil {
log.Println("error during unmount", err)
} else {
......@@ -65,11 +60,11 @@ func setupFs(fs pathfs.FileSystem, N int) (string, func()) {
}
func TestNewStatFs(t *testing.T) {
fs := NewStatFS()
fs := &StatFS{}
for _, n := range []string{
"file.txt", "sub/dir/foo.txt",
"sub/dir/bar.txt", "sub/marine.txt"} {
fs.AddFile(n)
fs.AddFile(n, fuse.Attr{Mode: syscall.S_IFREG})
}
wd, clean := setupFs(fs, 1)
......@@ -116,13 +111,13 @@ func TestNewStatFs(t *testing.T) {
func BenchmarkGoFuseStat(b *testing.B) {
b.StopTimer()
fs := NewStatFS()
fs := &StatFS{}
wd, _ := os.Getwd()
fileList := wd + "/testpaths.txt"
files := ReadLines(fileList)
for _, fn := range files {
fs.AddFile(fn)
fs.AddFile(fn, fuse.Attr{Mode: syscall.S_IFREG})
}
wd, clean := setupFs(fs, b.N)
......@@ -151,13 +146,13 @@ func readdir(d string) error {
func BenchmarkGoFuseReaddir(b *testing.B) {
b.StopTimer()
fs := NewStatFS()
fs := &StatFS{}
wd, _ := os.Getwd()
dirSet := map[string]struct{}{}
for _, fn := range ReadLines(wd + "/testpaths.txt") {
fs.AddFile(fn)
fs.AddFile(fn, fuse.Attr{Mode: syscall.S_IFREG})
dirSet[filepath.Dir(fn)] = struct{}{}
}
......
......@@ -5,65 +5,66 @@
package benchmark
import (
"context"
"path/filepath"
"strings"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/nodefs"
)
type StatFS struct {
pathfs.FileSystem
entries map[string]*fuse.Attr
dirs map[string][]fuse.DirEntry
nodefs.Inode
files map[string]fuse.Attr
}
func (fs *StatFS) Add(name string, a *fuse.Attr) {
name = strings.TrimRight(name, "/")
_, ok := fs.entries[name]
if ok {
return
var _ = (nodefs.OnAdder)((*StatFS)(nil))
func (r *StatFS) OnAdd(ctx context.Context) {
for nm, a := range r.files {
r.addFile(nm, a)
}
r.files = nil
}
fs.entries[name] = a
if name == "/" || name == "" {
return
func (r *StatFS) AddFile(name string, a fuse.Attr) {
if r.files == nil {
r.files = map[string]fuse.Attr{}
}
dir, base := filepath.Split(name)
dir = strings.TrimRight(dir, "/")
fs.dirs[dir] = append(fs.dirs[dir], fuse.DirEntry{Name: base, Mode: a.Mode})
fs.Add(dir, &fuse.Attr{Mode: fuse.S_IFDIR | 0755})
r.files[name] = a
}
func (fs *StatFS) AddFile(name string) {
fs.Add(name, &fuse.Attr{Mode: fuse.S_IFREG | 0644})
}
func (r *StatFS) addFile(name string, a fuse.Attr) {
dir, base := filepath.Split(name)
func (fs *StatFS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
if d := fs.dirs[name]; d != nil {
return &fuse.Attr{Mode: 0755 | fuse.S_IFDIR}, fuse.OK
}
e := fs.entries[name]
if e == nil {
return nil, fuse.ENOENT
}
p := r.EmbeddedInode()
return e, fuse.OK
}
// Add directories leading up to the file.
for _, component := range strings.Split(dir, "/") {
if len(component) == 0 {
continue
}
ch := p.GetChild(component)
if ch == nil {
// Create a directory
ch = p.NewPersistentInode(context.Background(), &nodefs.Inode{},
nodefs.NodeAttr{Mode: syscall.S_IFDIR})
// Add it
p.AddChild(component, ch, true)
}
func (fs *StatFS) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
entries := fs.dirs[name]
if entries == nil {
return nil, fuse.ENOENT
p = ch
}
return entries, fuse.OK
}
func NewStatFS() *StatFS {
return &StatFS{
FileSystem: pathfs.NewDefaultFileSystem(),
entries: make(map[string]*fuse.Attr),
dirs: make(map[string][]fuse.DirEntry),
}
// Create the file
child := p.NewPersistentInode(context.Background(), &nodefs.MemRegularFile{
Data: make([]byte, a.Size),
Attr: a,
}, nodefs.NodeAttr{})
// And add it
p.AddChild(base, child, true)
}
......@@ -7,59 +7,49 @@
package main
import (
"context"
"flag"
"log"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/nodefs"
)
type HelloFs struct {
pathfs.FileSystem
type HelloRoot struct {
nodefs.Inode
}
func (me *HelloFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
switch name {
case "file.txt":
return &fuse.Attr{
Mode: fuse.S_IFREG | 0644, Size: uint64(len(name)),
}, fuse.OK
case "":
return &fuse.Attr{
Mode: fuse.S_IFDIR | 0755,
}, fuse.OK
}
return nil, fuse.ENOENT
func (r *HelloRoot) OnAdd(ctx context.Context) {
ch := r.NewPersistentInode(
ctx, &nodefs.MemRegularFile{
Data: []byte("file.txt"),
Attr: fuse.Attr{
Mode: 0644,
},
}, nodefs.NodeAttr{Ino: 2})
r.AddChild("file.txt", ch, false)
}
func (me *HelloFs) OpenDir(name string, context *fuse.Context) (c []fuse.DirEntry, code fuse.Status) {
if name == "" {
c = []fuse.DirEntry{{Name: "file.txt", Mode: fuse.S_IFREG}}
return c, fuse.OK
}
return nil, fuse.ENOENT
func (r *HelloRoot) Getattr(ctx context.Context, fh nodefs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = 0755
return 0
}
func (me *HelloFs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
if name != "file.txt" {
return nil, fuse.ENOENT
}
if flags&fuse.O_ANYWRITE != 0 {
return nil, fuse.EPERM
}
return nodefs.NewDataFile([]byte(name)), fuse.OK
}
var _ = (nodefs.Getattrer)((*HelloRoot)(nil))
var _ = (nodefs.OnAdder)((*HelloRoot)(nil))
func main() {
debug := flag.Bool("debug", false, "print debug data")
flag.Parse()
if len(flag.Args()) < 1 {
log.Fatal("Usage:\n hello MOUNTPOINT")
}
nfs := pathfs.NewPathNodeFs(&HelloFs{FileSystem: pathfs.NewDefaultFileSystem()}, nil)
server, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), nil)
opts := &nodefs.Options{}
opts.Debug = *debug
server, err := nodefs.Mount(flag.Arg(0), &HelloRoot{}, opts)
if err != nil {
log.Fatalf("Mount fail: %v\n", err)
}
server.Serve()
server.Wait()
}
......@@ -14,14 +14,11 @@ import (
"os"
"os/signal"
"path"
"path/filepath"
"runtime/pprof"
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/nodefs"
)
func writeMemProfile(fn string, sigs <-chan os.Signal) {
......@@ -48,7 +45,6 @@ func main() {
// Scans the arg list and sets up flags
debug := flag.Bool("debug", false, "print debugging messages.")
other := flag.Bool("allow-other", false, "mount with -o allowother.")
enableLinks := flag.Bool("l", false, "Enable hard link support")
cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file")
memprofile := flag.String("memprofile", "", "write memory profile to this file")
flag.Parse()
......@@ -78,36 +74,26 @@ func main() {
fmt.Printf("Note: You must unmount gracefully, otherwise the profile file(s) will stay empty!\n")
}
var finalFs pathfs.FileSystem
orig := flag.Arg(1)
loopbackfs := pathfs.NewLoopbackFileSystem(orig)
finalFs = loopbackfs
loopbackRoot, err := nodefs.NewLoopbackRoot(orig)
if err != nil {
log.Fatalf("NewLoopbackRoot(%s): %v\n", orig, err)
}
sec := time.Second
opts := &nodefs.Options{
// These options are to be compatible with libfuse defaults,
// making benchmarking easier.
NegativeTimeout: time.Second,
AttrTimeout: time.Second,
EntryTimeout: time.Second,
}
// Enable ClientInodes so hard links work
pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: *enableLinks}
pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts)
conn := nodefs.NewFileSystemConnector(pathFs.Root(), opts)
mountPoint := flag.Arg(0)
origAbs, _ := filepath.Abs(orig)
mOpts := &fuse.MountOptions{
AllowOther: *other,
Name: "loopbackfs",
FsName: origAbs,
Debug: *debug,
AttrTimeout: &sec,
EntryTimeout: &sec,
}
state, err := fuse.NewServer(conn.RawFS(), mountPoint, mOpts)
opts.Debug = *debug
opts.AllowOther = *other
server, err := nodefs.Mount(flag.Arg(0), loopbackRoot, opts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
log.Fatalf("Mount fail: %v\n", err)
}
fmt.Println("Mounted!")
state.Serve()
server.Wait()
}
......@@ -9,9 +9,9 @@ import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/nodefs"
"github.com/hanwen/go-fuse/zipfs"
)
......@@ -25,15 +25,19 @@ func main() {
os.Exit(2)
}
fs := zipfs.NewMultiZipFs()
nfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions()
fs := &zipfs.MultiZipFs{}
sec := time.Second
opts := nodefs.Options{
EntryTimeout: &sec,
AttrTimeout: &sec,
DefaultPermissions: true,
}
opts.Debug = *debug
state, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), opts)
server, err := nodefs.Mount(flag.Arg(0), fs, &opts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
}
state.Serve()
server.Serve()
}
......@@ -14,11 +14,12 @@ import (
"runtime"
"runtime/pprof"
"strings"
"syscall"
"time"
"github.com/hanwen/go-fuse/benchmark"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/nodefs"
)
func main() {
......@@ -27,7 +28,7 @@ func main() {
profile := flag.String("profile", "", "record cpu profile.")
mem_profile := flag.String("mem-profile", "", "record memory profile.")
command := flag.String("run", "", "run this command after mounting.")
ttl := flag.Float64("ttl", 1.0, "attribute/entry cache TTL.")
ttl := flag.Duration("ttl", time.Second, "attribute/entry cache TTL.")
flag.Parse()
if flag.NArg() < 2 {
fmt.Fprintf(os.Stderr, "usage: %s MOUNTPOINT FILENAMES-FILE\n", os.Args[0])
......@@ -48,21 +49,21 @@ func main() {
log.Fatalf("os.Create: %v", err)
}
}
fs := benchmark.NewStatFS()
fs := &benchmark.StatFS{}
lines := benchmark.ReadLines(flag.Arg(1))
for _, l := range lines {
fs.AddFile(l)
fs.AddFile(strings.TrimSpace(l),
fuse.Attr{Mode: syscall.S_IFREG})
}
nfs := pathfs.NewPathNodeFs(fs, nil)
opts := &nodefs.Options{
AttrTimeout: time.Duration(*ttl * float64(time.Second)),
EntryTimeout: time.Duration(*ttl * float64(time.Second)),
Debug: *debug,
AttrTimeout: ttl,
EntryTimeout: ttl,
}
state, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), opts)
opts.Debug = *debug
server, err := nodefs.Mount(flag.Arg(0), fs, opts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
log.Fatalf("Mount fail: %v\n", err)
}
runtime.GC()
......@@ -78,7 +79,7 @@ func main() {
cmd.Start()
}
state.Serve()
server.Wait()
if memProfFile != nil {
pprof.WriteHeapProfile(memProfFile)
}
......
......@@ -16,7 +16,7 @@ import (
"strings"
"time"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/nodefs"
"github.com/hanwen/go-fuse/zipfs"
)
......@@ -26,7 +26,7 @@ func main() {
profile := flag.String("profile", "", "record cpu profile.")
mem_profile := flag.String("mem-profile", "", "record memory profile.")
command := flag.String("run", "", "run this command after mounting.")
ttl := flag.Float64("ttl", 1.0, "attribute/entry cache TTL.")
ttl := flag.Duration("ttl", time.Second, "attribute/entry cache TTL.")
flag.Parse()
if flag.NArg() < 2 {
fmt.Fprintf(os.Stderr, "usage: %s MOUNTPOINT ZIP-FILE\n", os.Args[0])
......@@ -55,11 +55,12 @@ func main() {
}
opts := &nodefs.Options{
AttrTimeout: time.Duration(*ttl * float64(time.Second)),
EntryTimeout: time.Duration(*ttl * float64(time.Second)),
Debug: *debug,
AttrTimeout: ttl,
EntryTimeout: ttl,
DefaultPermissions: true,
}
state, _, err := nodefs.MountRoot(flag.Arg(0), root, opts)
opts.Debug = *debug
server, err := nodefs.Mount(flag.Arg(0), root, opts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
......@@ -78,7 +79,7 @@ func main() {
cmd.Start()
}
state.Serve()
server.Wait()
if memProfFile != nil {
pprof.WriteHeapProfile(memProfFile)
}
......
......@@ -15,60 +15,60 @@ import (
)
const (
_OP_LOOKUP = int32(1)
_OP_FORGET = int32(2)
_OP_GETATTR = int32(3)
_OP_SETATTR = int32(4)
_OP_READLINK = int32(5)
_OP_SYMLINK = int32(6)
_OP_MKNOD = int32(8)
_OP_MKDIR = int32(9)
_OP_UNLINK = int32(10)
_OP_RMDIR = int32(11)
_OP_RENAME = int32(12)
_OP_LINK = int32(13)
_OP_OPEN = int32(14)
_OP_READ = int32(15)
_OP_WRITE = int32(16)
_OP_STATFS = int32(17)
_OP_RELEASE = int32(18)
_OP_FSYNC = int32(20)
_OP_SETXATTR = int32(21)
_OP_GETXATTR = int32(22)
_OP_LISTXATTR = int32(23)
_OP_REMOVEXATTR = int32(24)
_OP_FLUSH = int32(25)
_OP_INIT = int32(26)
_OP_OPENDIR = int32(27)
_OP_READDIR = int32(28)
_OP_RELEASEDIR = int32(29)
_OP_FSYNCDIR = int32(30)
_OP_GETLK = int32(31)
_OP_SETLK = int32(32)
_OP_SETLKW = int32(33)
_OP_ACCESS = int32(34)
_OP_CREATE = int32(35)
_OP_INTERRUPT = int32(36)
_OP_BMAP = int32(37)
_OP_DESTROY = int32(38)
_OP_IOCTL = int32(39)
_OP_POLL = int32(40)
_OP_NOTIFY_REPLY = int32(41)
_OP_BATCH_FORGET = int32(42)
_OP_FALLOCATE = int32(43) // protocol version 19.
_OP_READDIRPLUS = int32(44) // protocol version 21.
_OP_RENAME2 = int32(45) // protocol version 23.
_OP_LSEEK = int32(46)
_OP_COPY_FILE_RANGE = int32(47)
_OP_LOOKUP = uint32(1)
_OP_FORGET = uint32(2)
_OP_GETATTR = uint32(3)
_OP_SETATTR = uint32(4)
_OP_READLINK = uint32(5)
_OP_SYMLINK = uint32(6)
_OP_MKNOD = uint32(8)
_OP_MKDIR = uint32(9)
_OP_UNLINK = uint32(10)
_OP_RMDIR = uint32(11)
_OP_RENAME = uint32(12)
_OP_LINK = uint32(13)
_OP_OPEN = uint32(14)
_OP_READ = uint32(15)
_OP_WRITE = uint32(16)
_OP_STATFS = uint32(17)
_OP_RELEASE = uint32(18)
_OP_FSYNC = uint32(20)
_OP_SETXATTR = uint32(21)
_OP_GETXATTR = uint32(22)
_OP_LISTXATTR = uint32(23)
_OP_REMOVEXATTR = uint32(24)
_OP_FLUSH = uint32(25)
_OP_INIT = uint32(26)
_OP_OPENDIR = uint32(27)
_OP_READDIR = uint32(28)
_OP_RELEASEDIR = uint32(29)
_OP_FSYNCDIR = uint32(30)
_OP_GETLK = uint32(31)
_OP_SETLK = uint32(32)
_OP_SETLKW = uint32(33)
_OP_ACCESS = uint32(34)
_OP_CREATE = uint32(35)
_OP_INTERRUPT = uint32(36)
_OP_BMAP = uint32(37)
_OP_DESTROY = uint32(38)
_OP_IOCTL = uint32(39)
_OP_POLL = uint32(40)
_OP_NOTIFY_REPLY = uint32(41)
_OP_BATCH_FORGET = uint32(42)
_OP_FALLOCATE = uint32(43) // protocol version 19.
_OP_READDIRPLUS = uint32(44) // protocol version 21.
_OP_RENAME2 = uint32(45) // protocol version 23.
_OP_LSEEK = uint32(46)
_OP_COPY_FILE_RANGE = uint32(47)
// The following entries don't have to be compatible across Go-FUSE versions.
_OP_NOTIFY_INVAL_ENTRY = int32(100)
_OP_NOTIFY_INVAL_INODE = int32(101)
_OP_NOTIFY_STORE_CACHE = int32(102)
_OP_NOTIFY_RETRIEVE_CACHE = int32(103)
_OP_NOTIFY_DELETE = int32(104) // protocol version 18
_OP_NOTIFY_INVAL_ENTRY = uint32(100)
_OP_NOTIFY_INVAL_INODE = uint32(101)
_OP_NOTIFY_STORE_CACHE = uint32(102)
_OP_NOTIFY_RETRIEVE_CACHE = uint32(103)
_OP_NOTIFY_DELETE = uint32(104) // protocol version 18
_OPCODE_COUNT = int32(105)
_OPCODE_COUNT = uint32(105)
)
////////////////////////////////////////////////////////////////
......@@ -510,7 +510,7 @@ type operationHandler struct {
var operationHandlers []*operationHandler
func operationName(op int32) string {
func operationName(op uint32) string {
h := getHandler(op)
if h == nil {
return "unknown"
......@@ -518,7 +518,7 @@ func operationName(op int32) string {
return h.Name
}
func getHandler(o int32) *operationHandler {
func getHandler(o uint32) *operationHandler {
if o >= _OPCODE_COUNT {
return nil
}
......@@ -531,12 +531,12 @@ func init() {
operationHandlers[i] = &operationHandler{Name: fmt.Sprintf("OPCODE-%d", i)}
}
fileOps := []int32{_OP_READLINK, _OP_NOTIFY_INVAL_ENTRY, _OP_NOTIFY_DELETE}
fileOps := []uint32{_OP_READLINK, _OP_NOTIFY_INVAL_ENTRY, _OP_NOTIFY_DELETE}
for _, op := range fileOps {
operationHandlers[op].FileNameOut = true
}
for op, sz := range map[int32]uintptr{
for op, sz := range map[uint32]uintptr{
_OP_FORGET: unsafe.Sizeof(ForgetIn{}),
_OP_BATCH_FORGET: unsafe.Sizeof(_BatchForgetIn{}),
_OP_GETATTR: unsafe.Sizeof(GetAttrIn{}),
......@@ -578,7 +578,7 @@ func init() {
operationHandlers[op].InputSize = sz
}
for op, sz := range map[int32]uintptr{
for op, sz := range map[uint32]uintptr{
_OP_LOOKUP: unsafe.Sizeof(EntryOut{}),
_OP_GETATTR: unsafe.Sizeof(AttrOut{}),
_OP_SETATTR: unsafe.Sizeof(AttrOut{}),
......@@ -609,7 +609,7 @@ func init() {
operationHandlers[op].OutputSize = sz
}
for op, v := range map[int32]string{
for op, v := range map[uint32]string{
_OP_LOOKUP: "LOOKUP",
_OP_FORGET: "FORGET",
_OP_BATCH_FORGET: "BATCH_FORGET",
......@@ -664,7 +664,7 @@ func init() {
operationHandlers[op].Name = v
}
for op, v := range map[int32]operationFunc{
for op, v := range map[uint32]operationFunc{
_OP_OPEN: doOpen,
_OP_READDIR: doReadDir,
_OP_WRITE: doWrite,
......@@ -713,7 +713,7 @@ func init() {
}
// Outputs.
for op, f := range map[int32]castPointerFunc{
for op, f := range map[uint32]castPointerFunc{
_OP_LOOKUP: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) },
_OP_OPEN: func(ptr unsafe.Pointer) interface{} { return (*OpenOut)(ptr) },
_OP_OPENDIR: func(ptr unsafe.Pointer) interface{} { return (*OpenOut)(ptr) },
......@@ -738,7 +738,7 @@ func init() {
}
// Inputs.
for op, f := range map[int32]castPointerFunc{
for op, f := range map[uint32]castPointerFunc{
_OP_FLUSH: func(ptr unsafe.Pointer) interface{} { return (*FlushIn)(ptr) },
_OP_GETATTR: func(ptr unsafe.Pointer) interface{} { return (*GetAttrIn)(ptr) },
_OP_SETXATTR: func(ptr unsafe.Pointer) interface{} { return (*SetXAttrIn)(ptr) },
......@@ -775,7 +775,7 @@ func init() {
}
// File name args.
for op, count := range map[int32]int{
for op, count := range map[uint32]int{
_OP_CREATE: 1,
_OP_SETXATTR: 1,
_OP_GETXATTR: 1,
......
......@@ -81,7 +81,7 @@ const debugDataDumpMax = 8 // maximum #bytes of request/response data to dump
func (r *request) InputDebug() string {
val := ""
if r.handler.DecodeIn != nil {
if r.handler != nil && r.handler.DecodeIn != nil {
val = fmt.Sprintf("%v ", Print(r.handler.DecodeIn(r.inData)))
}
......@@ -107,7 +107,7 @@ func (r *request) InputDebug() string {
func (r *request) OutputDebug() string {
var dataStr string
if r.handler.DecodeOut != nil && r.handler.OutputSize > 0 {
if r.handler != nil && r.handler.DecodeOut != nil && r.handler.OutputSize > 0 {
dataStr = Print(r.handler.DecodeOut(r.outData()))
}
......@@ -118,7 +118,7 @@ func (r *request) OutputDebug() string {
flatStr := ""
if r.flatDataSize() > 0 {
if r.handler.FileNameOut {
if r.handler != nil && r.handler.FileNameOut {
s := strings.TrimRight(string(r.flatData), "\x00")
flatStr = fmt.Sprintf(" %q", s)
} else {
......
......@@ -603,7 +603,7 @@ type Caller struct {
type InHeader struct {
Length uint32
Opcode int32
Opcode uint32
Unique uint64
NodeId uint64
Caller
......
......@@ -60,3 +60,5 @@ To do/To decide
* decide on a final package name
* handle less open/create.
* Symlink []byte vs string.
......@@ -28,32 +28,47 @@
// expressed through index-nodes (also known as "inode", see Inode) which
// describe parent/child relation in between nodes and node-ID association.
//
// A particular filesystem should provide nodes with filesystem
// operations implemented as defined by Operations interface. When
// filesystem is mounted, its root Operations is associated with root
// of the tree, and the tree is further build lazily when nodefs
// infrastructure needs to lookup children of nodes to process client
// requests. For every new Operations, the filesystem infrastructure
// automatically builds new index node and links it in the filesystem
// tree. Operations.Inode() can be used to get particular Inode
// associated with a Operations.
// The filesystem nodes are struct that embed the Inode type, so they
// comply with the InodeEmbedder interface. They should be
// initialized by calling NewInode or NewPersistentInode before being
// manipulated further, eg.
//
//
// type myNode struct {
// Inode
// }
//
// func (n *myNode) Lookup(ctx context.Context, name string, ... ) (*Inode, syscall.Errno) {
// child := myNode{}
// return n.NewInode(ctx, &myNode{}, NodeAttr{Mode: syscall.S_IFDIR}), 0
// }
//
// On mounting, the root InodeEmbedder is associated with root of the
// tree.
//
// The kernel can evict inode data to free up memory. It does so by
// issuing FORGET calls. When a node has no children, and no kernel
// references, it is removed from the file system trees.
//
// File system trees can also be constructed in advance. This is done
// by instantiating "persistent" inodes from the Operations.OnAdd
// method. Persistent inodes remain in memory even if the kernel has
// forgotten them. See zip_test.go for an example of how to do this.
// by instantiating "persistent" inodes from the OnAdder
// implementation. Persistent inodes remain in memory even if the
// kernel has forgotten them. See zip_test.go for an example of how
// to do this.
//
// File systems whose tree structures are on backing storage typically
// discover the file system tree on-demand, and if the kernel is tight
// on memory, parts of the tree are forgotten again. These file
// systems should implement Operations.Lookup instead. The loopback
// file system created by `NewLoopbackRoot` provides a straightforward
// systems should implement Lookuper instead. The loopback file
// system created by `NewLoopbackRoot` provides a straightforward
// example.
//
// All error reporting must use the syscall.Errno type. The value 0
// (`OK`) should be used to indicate success. The method names are
// inspired on the system call names, so we have Listxattr rather than
// ListXAttr.
//
package nodefs
import (
......@@ -64,160 +79,183 @@ import (
"github.com/hanwen/go-fuse/fuse"
)
// InodeLink provides the machinery to connect Operations (user
// defined methods) to Inode (a node in the filesystem tree).
type InodeLink interface {
// InodeEmbedder is an interface for structs that embed Inode.
//
// In general, if an InodeEmbedder does not implement specific
// filesystem methods, the filesystem will react as if it is a
// read-only filesystem with a predefined tree structure. See
// zipfs_test.go for an example. A example is in zip_test.go
type InodeEmbedder interface {
// populateInode and inode are used by nodefs internally to
// link Inode to a Node.
//
// See Inode() for the public API to retrieve an inode from Node.
inode() *Inode
init(ops Operations, attr NodeAttr, bridge *rawBridge, persistent bool)
embed() *Inode
// Inode returns the *Inode associated with this Operations
// instance. The identity of the Inode does not change over
// the lifetime of the node object. Inode() is provided by
// OperationStubs, and should not be reimplemented.
Inode() *Inode
// EmbeddedInode returns a pointer to the embedded inode.
EmbeddedInode() *Inode
}
// Operations is the interface that implements the filesystem inode.
// Each Operations instance must embed OperationStubs. All error
// reporting must use the syscall.Errno type. The value 0 (`OK`)
// should be used to indicate success. The method names are inspired
// on the system call names, so we have Listxattr rather than
// ListXAttr.
type Operations interface {
InodeLink
// Statfs implements statistics for the filesystem that holds
// this Inode.
// Statfs implements statistics for the filesystem that holds this
// Inode. If not defined, the `out` argument will zeroed with an OK
// result. This is because OSX filesystems must Statfs, or the mount
// will not work.
type Statfser interface {
Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno
}
// Access should return if the caller can access the file with
// the given mode. In this case, the context has data about
// the real UID. For example a root-SUID binary called by user
// susan gets the UID and GID for susan here.
// Access should return if the caller can access the file with the
// given mode. This is used for two purposes: to determine if a user
// may enter a directory, and to answer to implement the access system
// call. In the latter case, the context has data about the real
// UID. For example, a root-SUID binary called by user susan gets the
// UID and GID for susan here.
//
// If not defined, a default implementation will check traditional
// unix permissions of the Getattr result agains the caller. If so, it
// is necessary to either return permissions from GetAttr/Lookup or
// set Options.DefaultPermissions in order to allow chdir into the
// FUSE mount.
type Accesser interface {
Access(ctx context.Context, mask uint32) syscall.Errno
}
// GetAttr reads attributes for an Inode. The library will
// ensure that Mode and Ino are set correctly. For regular
// files, Size should be set so it can be read correctly.
Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno
// GetAttr reads attributes for an Inode. The library will
// ensure that Mode and Ino are set correctly. For regular
// files, Size should be set so it can be read correctly.
type Getattrer interface {
Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
}
// SetAttr sets attributes for an Inode.
Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
// SetAttr sets attributes for an Inode.
type Setattrer interface {
Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
}
// OnAdd is called once this Operations object is attached to
// an Inode.
// OnAdd is called when this InodeEmbedder is initialized.
type OnAdder interface {
OnAdd(ctx context.Context)
}
// XAttrOperations is a collection of methods used to implement extended attributes.
type XAttrOperations interface {
Operations
// GetXAttr should read data for the given attribute into
// `dest` and return the number of bytes. If `dest` is too
// small, it should return ERANGE and the size of the attribute.
// Getxattr should read data for the given attribute into
// `dest` and return the number of bytes. If `dest` is too
// small, it should return ERANGE and the size of the attribute.
// If not defined, Getxattr will return ENOATTR.
type Getxattrer interface {
Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno)
}
// SetXAttr should store data for the given attribute. See
// setxattr(2) for information about flags.
// Setxattr should store data for the given attribute. See
// setxattr(2) for information about flags.
// If not defined, Setxattr will return ENOATTR.
type Setxattrer interface {
Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno
}
// RemoveXAttr should delete the given attribute.
// Removexattr should delete the given attribute.
// If not defined, Removexattr will return ENOATTR.
type Removexattrer interface {
Removexattr(ctx context.Context, attr string) syscall.Errno
}
// ListXAttr should read all attributes (null terminated) into
// `dest`. If the `dest` buffer is too small, it should return
// ERANGE and the correct size.
// Listxattr should read all attributes (null terminated) into
// `dest`. If the `dest` buffer is too small, it should return ERANGE
// and the correct size. If not defined, return an empty list and
// success.
type Listxattrer interface {
Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno)
}
// SymlinkOperations holds operations specific to symlinks.
type SymlinkOperations interface {
Operations
// Readlink reads the content of a symlink.
// Readlink reads the content of a symlink.
type Readlinker interface {
Readlink(ctx context.Context) ([]byte, syscall.Errno)
}
// FileOperations holds operations that apply to regular files.
type FileOperations interface {
Operations
// Open opens an Inode (of regular file type) for reading. It
// is optional but recommended to return a FileHandle.
// Open opens an Inode (of regular file type) for reading. It
// is optional but recommended to return a FileHandle.
type Opener interface {
Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
}
// Reads data from a file. The data should be returned as
// ReadResult, which may be constructed from the incoming
// `dest` buffer. If the file was opened without FileHandle,
// the FileHandle argument here is nil. The default
// implementation forwards to the FileHandle.
// Reads data from a file. The data should be returned as
// ReadResult, which may be constructed from the incoming
// `dest` buffer. If the file was opened without FileHandle,
// the FileHandle argument here is nil. The default
// implementation forwards to the FileHandle.
type Reader interface {
Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
}
// Writes the data into the file handle at given offset. After
// returning, the data will be reused and may not referenced.
// The default implementation forwards to the FileHandle.
// Writes the data into the file handle at given offset. After
// returning, the data will be reused and may not referenced.
// The default implementation forwards to the FileHandle.
type Writer interface {
Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, errno syscall.Errno)
}
// Fsync is a signal to ensure writes to the Inode are flushed
// to stable storage. The default implementation forwards to the
// FileHandle.
// Fsync is a signal to ensure writes to the Inode are flushed
// to stable storage.
type Fsyncer interface {
Fsync(ctx context.Context, f FileHandle, flags uint32) syscall.Errno
}
// Flush is called for close() call on a file descriptor. In
// case of duplicated descriptor, it may be called more than
// once for a file. The default implementation forwards to the
// FileHandle.
// Flush is called for close() call on a file descriptor. In
// case of duplicated descriptor, it may be called more than
// once for a file. The default implementation forwards to the
// FileHandle.
type Flusher interface {
Flush(ctx context.Context, f FileHandle) syscall.Errno
}
// This is called to before the file handle is forgotten. The
// kernel ingores the return value of this method,
// so any cleanup that requires specific synchronization or
// could fail with I/O errors should happen in Flush instead.
// The default implementation forwards to the FileHandle.
// This is called to before the file handle is forgotten. The
// kernel ignores the return value of this method,
// so any cleanup that requires specific synchronization or
// could fail with I/O errors should happen in Flush instead.
// The default implementation forwards to the FileHandle.
type Releaser interface {
Release(ctx context.Context, f FileHandle) syscall.Errno
}
// Allocate preallocates space for future writes, so they will
// never encounter ESPACE.
// Allocate preallocates space for future writes, so they will
// never encounter ESPACE.
type Allocater interface {
Allocate(ctx context.Context, f FileHandle, off uint64, size uint64, mode uint32) syscall.Errno
}
// FGetattr is like Getattr but provides a file handle if available.
Fgetattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
// FSetattr is like SetAttr but provides a file handle if available.
Fsetattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
// CopyFileRange copies data between sections of two files,
// without the data having to pass through the calling process.
// CopyFileRange copies data between sections of two files,
// without the data having to pass through the calling process.
type CopyFileRanger interface {
CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno)
}
// Lseek is used to implement holes: it should return the
// first offset beyond `off` where there is data (SEEK_DATA)
// or where there is a hole (SEEK_HOLE).
// Lseek is used to implement holes: it should return the
// first offset beyond `off` where there is data (SEEK_DATA)
// or where there is a hole (SEEK_HOLE).
type Lseeker interface {
Lseek(ctx context.Context, f FileHandle, Off uint64, whence uint32) (uint64, syscall.Errno)
}
// LockOperations are operations for locking regions of regular files.
type LockOperations interface {
FileOperations
// Getlk returns locks that would conflict with the given
// input lock. If no locks conflict, the output has type
// L_UNLCK. See fcntl(2) for more information.
// Getlk returns locks that would conflict with the given input
// lock. If no locks conflict, the output has type L_UNLCK. See
// fcntl(2) for more information.
// If not defined, returns ENOTSUP
type Getlker interface {
Getlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno
}
// Setlk obtains a lock on a file, or fail if the lock could not
// obtained. See fcntl(2) for more information.
// Setlk obtains a lock on a file, or fail if the lock could not
// obtained. See fcntl(2) for more information. If not defined,
// returns ENOTSUP
type Setlker interface {
Setlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
}
// Setlkw obtains a lock on a file, waiting if necessary. See fcntl(2)
// for more information.
// Setlkw obtains a lock on a file, waiting if necessary. See fcntl(2)
// for more information. If not defined, returns ENOTSUP
type Setlkwer interface {
Setlkw(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
}
......@@ -237,63 +275,87 @@ type DirStream interface {
Close()
}
// DirOperations are operations for directory nodes in the filesystem.
type DirOperations interface {
Operations
// Lookup should find a direct child of the node by child
// name. If the entry does not exist, it should return ENOENT
// and optionally set a NegativeTimeout in `out`. If it does
// exist, it should return attribute data in `out` and return
// the Inode for the child. A new inode can be created using
// `Inode.NewInode`. The new Inode will be added to the FS
// tree automatically if the return status is OK.
// Lookup should find a direct child of the node by child
// name. If the entry does not exist, it should return ENOENT
// and optionally set a NegativeTimeout in `out`. If it does
// exist, it should return attribute data in `out` and return
// the Inode for the child. A new inode can be created using
// `Inode.NewInode`. The new Inode will be added to the FS
// tree automatically if the return status is OK.
//
// If not defined, we look for an existing child with the given name,
// or returns ENOENT.
type Lookuper interface {
Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)
}
// OpenDir opens a directory Inode for reading its
// contents. The actual reading is driven from ReadDir, so
// this method is just for performing sanity/permission
// checks.
// OpenDir opens a directory Inode for reading its
// contents. The actual reading is driven from ReadDir, so
// this method is just for performing sanity/permission
// checks. The default is to return success.
type Opendirer interface {
Opendir(ctx context.Context) syscall.Errno
}
// ReadDir opens a stream of directory entries.
// ReadDir opens a stream of directory entries.
//
// The default ReadDir returns the list of currently known children
// from the tree
type Readdirer interface {
Readdir(ctx context.Context) (DirStream, syscall.Errno)
}
// MutableDirOperations are operations for directories that can add or
// remove entries.
type MutableDirOperations interface {
DirOperations
// Mkdir is similar to Lookup, but must create a directory entry and Inode.
// Mkdir is similar to Lookup, but must create a directory entry and Inode.
// Default is to return EROFS.
type Mkdirer interface {
Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
}
// Mknod is similar to Lookup, but must create a device entry and Inode.
// Mknod is similar to Lookup, but must create a device entry and Inode.
// Default is to return EROFS.
type Mknoder interface {
Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
}
// Link is similar to Lookup, but must create a new link to an existing Inode.
Link(ctx context.Context, target Operations, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
// Link is similar to Lookup, but must create a new link to an existing Inode.
// Default is to return EROFS.
type Linker interface {
Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
}
// Symlink is similar to Lookup, but must create a new symbolic link.
// Symlink is similar to Lookup, but must create a new symbolic link.
// Default is to return EROFS.
type Symlinker interface {
Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
}
// Create is similar to Lookup, but should create a new
// child. It typically also returns a FileHandle as a
// reference for future reads/writes
// Create is similar to Lookup, but should create a new
// child. It typically also returns a FileHandle as a
// reference for future reads/writes.
// Default is to return EROFS.
type Creater interface {
Create(ctx context.Context, name string, flags uint32, mode uint32) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno)
}
// Unlink should remove a child from this directory. If the
// return status is OK, the Inode is removed as child in the
// FS tree automatically.
// Unlink should remove a child from this directory. If the
// return status is OK, the Inode is removed as child in the
// FS tree automatically. Default is to return EROFS.
type Unlinker interface {
Unlink(ctx context.Context, name string) syscall.Errno
}
// Rmdir is like Unlink but for directories.
// Rmdir is like Unlink but for directories.
// Default is to return EROFS.
type Rmdirer interface {
Rmdir(ctx context.Context, name string) syscall.Errno
}
// Rename should move a child from one directory to a
// different one. The changes is effected in the FS tree if
// the return status is OK
Rename(ctx context.Context, name string, newParent Operations, newName string, flags uint32) syscall.Errno
// Rename should move a child from one directory to a different
// one. The change is effected in the FS tree if the return status is
// OK.
// Default is to return EROFS.
type Renamer interface {
Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno
}
// FileHandle is a resource identifier for opened files. FileHandles
......@@ -307,28 +369,67 @@ type MutableDirOperations interface {
// FileHandle. Files that have such dynamic content should return the
// FOPEN_DIRECT_IO flag from their `Open` method. See directio_test.go
// for an example.
//
// For a description of individual operations, see the equivalent
// operations in FileOperations.
type FileHandle interface {
}
// Release is called when forgetting the file handle. Default is to
// call Release on the Inode.
type FileReleaser interface {
Release(ctx context.Context) syscall.Errno
}
// See Getattrer. Default is to call Getattr on the Inode
type FileGetattrer interface {
Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno
}
// See Reader. Default is to call Read on the Inode
type FileReader interface {
Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
}
// See Writer. Default is to call Write on the Inode
type FileWriter interface {
Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno)
}
// See Getlker. Default is to call Getlk on the Inode
type FileGetlker interface {
Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno
}
// See Setlker. Default is to call Setlk on the Inode
type FileSetlker interface {
Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
}
// See Setlkwer. Default is to call Setlkw on the Inode
type FileSetlkwer interface {
Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
}
// See Lseek. Default is to call Lseek on the Inode
type FileLseeker interface {
Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno)
}
// See Flusher. Default is to call Flush on the Inode
type FileFlusher interface {
Flush(ctx context.Context) syscall.Errno
}
// See Fsync. Default is to call Fsync on the Inode
type FileFsyncer interface {
Fsync(ctx context.Context, flags uint32) syscall.Errno
}
Release(ctx context.Context) syscall.Errno
Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno
// See Fsync. Default is to call Setattr on the Inode
type FileSetattrer interface {
Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
}
// See Allocater. Default is to call Allocate on the Inode
type FileAllocater interface {
Allocate(ctx context.Context, off uint64, size uint64, mode uint32) syscall.Errno
}
......@@ -354,4 +455,12 @@ type Options struct {
// Automatic inode numbers are handed out sequentially
// starting from this number. If unset, use 2^63.
FirstAutomaticIno uint64
// OnAdd is an alternative way to specify the OnAdd
// functionality of the root node.
OnAdd func(ctx context.Context)
// DefaultPermissions sets null file permissions to 755 (dirs)
// or 644 (other files.)
DefaultPermissions bool
}
......@@ -12,6 +12,7 @@ import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal"
)
func errnoToStatus(errno syscall.Errno) fuse.Status {
......@@ -48,7 +49,7 @@ type rawBridge struct {
}
// newInode creates creates new inode pointing to ops.
func (b *rawBridge) newInode(ctx context.Context, ops Operations, id NodeAttr, persistent bool) *Inode {
func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id NodeAttr, persistent bool) *Inode {
b.mu.Lock()
defer b.mu.Unlock()
......@@ -57,8 +58,8 @@ func (b *rawBridge) newInode(ctx context.Context, ops Operations, id NodeAttr, p
}
// This ops already was populated. Just return it.
if ops.inode().bridge != nil {
return ops.inode()
if ops.embed().bridge != nil {
return ops.embed()
}
if id.Ino == 0 {
......@@ -93,27 +94,21 @@ func (b *rawBridge) newInode(ctx context.Context, ops Operations, id NodeAttr, p
id.Mode = fuse.S_IFREG
}
switch id.Mode {
case fuse.S_IFDIR:
_ = ops.(DirOperations)
case fuse.S_IFLNK:
_ = ops.(SymlinkOperations)
case fuse.S_IFREG:
_ = ops.(FileOperations)
case fuse.S_IFIFO, syscall.S_IFSOCK:
// no type check necessary: FIFO and SOCK don't go
// through FUSE for open/read etc.
break
default:
// Remaining types are char and block devices. Not
// sure how those would work in FUSE
log.Panicf("filetype %o unimplemented", id.Mode)
b.nodes[id.Ino] = ops.embed()
initInode(ops.embed(), ops, id, b, persistent)
return ops.embed()
}
func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id NodeAttr, persistent bool) *Inode {
ch := b.newInodeUnlocked(ops, id, persistent)
if ch != ops.embed() {
return ch
}
b.nodes[id.Ino] = ops.inode()
ops.init(ops, id, b, persistent)
ops.OnAdd(ctx)
return ops.inode()
if oa, ok := ops.(OnAdder); ok {
oa.OnAdd(ctx)
}
return ch
}
// addNewChild inserts the child into the tree. Returns file handle if file != nil.
......@@ -139,6 +134,7 @@ func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file F
}
func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) {
b.setAttr(&out.Attr)
if b.options.AttrTimeout != nil && out.AttrTimeout() == 0 {
out.SetAttrTimeout(*b.options.AttrTimeout)
}
......@@ -147,6 +143,15 @@ func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) {
}
}
func (b *rawBridge) setAttr(out *fuse.Attr) {
if b.options.DefaultPermissions && out.Mode&07777 == 0 {
out.Mode |= 0644
if out.Mode&syscall.S_IFDIR != 0 {
out.Mode |= 0111
}
}
}
func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) {
if b.options.AttrTimeout != nil && out.Timeout() == 0 {
out.SetTimeout(*b.options.AttrTimeout)
......@@ -155,7 +160,7 @@ func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) {
// NewNodeFS creates a node based filesystem based on an Operations
// instance for the root.
func NewNodeFS(root DirOperations, opts *Options) fuse.RawFileSystem {
func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem {
bridge := &rawBridge{
automaticIno: opts.FirstAutomaticIno,
}
......@@ -175,7 +180,7 @@ func NewNodeFS(root DirOperations, opts *Options) fuse.RawFileSystem {
bridge.options.AttrTimeout = &oneSec
}
root.init(root,
initInode(root.embed(), root,
NodeAttr{
Ino: 1,
Mode: fuse.S_IFDIR,
......@@ -183,7 +188,7 @@ func NewNodeFS(root DirOperations, opts *Options) fuse.RawFileSystem {
bridge,
false,
)
bridge.root = root.inode()
bridge.root = root.embed()
bridge.root.lookupCount = 1
bridge.nodes = map[uint64]*Inode{
1: bridge.root,
......@@ -192,7 +197,11 @@ func NewNodeFS(root DirOperations, opts *Options) fuse.RawFileSystem {
// Fh 0 means no file handle.
bridge.files = []*fileEntry{{}}
root.OnAdd(context.Background())
if opts.OnAdd != nil {
opts.OnAdd(context.Background())
} else if oa, ok := root.(OnAdder); ok {
oa.OnAdd(context.Background())
}
return bridge
}
......@@ -213,8 +222,9 @@ func (b *rawBridge) inode(id uint64, fh uint64) (*Inode, *fileEntry) {
func (b *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name string, out *fuse.EntryOut) fuse.Status {
parent, _ := b.inode(header.NodeId, 0)
ctx := &fuse.Context{Caller: header.Caller, Cancel: cancel}
child, errno := b.lookup(ctx, parent, name, out)
child, errno := parent.dirOps().Lookup(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name, out)
if errno != 0 {
if b.options.NegativeTimeout != nil && out.EntryTimeout() == 0 {
out.SetEntryTimeout(*b.options.NegativeTimeout)
......@@ -222,17 +232,38 @@ func (b *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name s
return errnoToStatus(errno)
}
child.setEntryOut(out)
b.addNewChild(parent, name, child, nil, 0, out)
b.setEntryOutTimeout(out)
out.Mode = child.nodeAttr.Mode | (out.Mode & 07777)
return fuse.OK
}
func (b *rawBridge) lookup(ctx *fuse.Context, parent *Inode, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
if lu, ok := parent.ops.(Lookuper); ok {
return lu.Lookup(ctx, name, out)
}
child := parent.GetChild(name)
if child == nil {
return nil, syscall.ENOENT
}
if ga, ok := child.ops.(Getattrer); ok {
var a fuse.AttrOut
errno := ga.Getattr(ctx, nil, &a)
if errno == 0 {
out.Attr = a.Attr
}
}
return child, OK
}
func (b *rawBridge) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name string) fuse.Status {
parent, _ := b.inode(header.NodeId, 0)
var errno syscall.Errno
if mops, ok := parent.ops.(MutableDirOperations); ok {
if mops, ok := parent.ops.(Rmdirer); ok {
errno = mops.Rmdir(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name)
}
......@@ -245,7 +276,7 @@ func (b *rawBridge) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name st
func (b *rawBridge) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name string) fuse.Status {
parent, _ := b.inode(header.NodeId, 0)
var errno syscall.Errno
if mops, ok := parent.ops.(MutableDirOperations); ok {
if mops, ok := parent.ops.(Unlinker); ok {
errno = mops.Unlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name)
}
......@@ -260,7 +291,7 @@ func (b *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name stri
var child *Inode
var errno syscall.Errno
if mops, ok := parent.ops.(MutableDirOperations); ok {
if mops, ok := parent.ops.(Mkdirer); ok {
child, errno = mops.Mkdir(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, out)
}
......@@ -272,6 +303,7 @@ func (b *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name stri
log.Panicf("Mkdir: mode must be S_IFDIR (%o), got %o", fuse.S_IFDIR, out.Attr.Mode)
}
child.setEntryOut(out)
b.addNewChild(parent, name, child, nil, 0, out)
b.setEntryOutTimeout(out)
return fuse.OK
......@@ -282,7 +314,7 @@ func (b *rawBridge) Mknod(cancel <-chan struct{}, input *fuse.MknodIn, name stri
var child *Inode
var errno syscall.Errno
if mops, ok := parent.ops.(MutableDirOperations); ok {
if mops, ok := parent.ops.(Mknoder); ok {
child, errno = mops.Mknod(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, input.Rdev, out)
}
......@@ -290,6 +322,7 @@ func (b *rawBridge) Mknod(cancel <-chan struct{}, input *fuse.MknodIn, name stri
return errnoToStatus(errno)
}
child.setEntryOut(out)
b.addNewChild(parent, name, child, nil, 0, out)
b.setEntryOutTimeout(out)
return fuse.OK
......@@ -303,8 +336,10 @@ func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name st
var errno syscall.Errno
var f FileHandle
var flags uint32
if mops, ok := parent.ops.(MutableDirOperations); ok {
if mops, ok := parent.ops.(Creater); ok {
child, f, flags, errno = mops.Create(ctx, name, input.Flags, input.Mode)
} else {
return fuse.EROFS
}
if errno != 0 {
......@@ -315,21 +350,18 @@ func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name st
}
out.Fh = uint64(b.addNewChild(parent, name, child, f, input.Flags|syscall.O_CREAT, &out.EntryOut))
b.setEntryOutTimeout(&out.EntryOut)
out.OpenFlags = flags
var temp fuse.AttrOut
f.Getattr(ctx, &temp)
b.getattr(ctx, child, f, &temp)
out.Attr = temp.Attr
out.AttrValid = temp.AttrValid
out.AttrValidNsec = temp.AttrValidNsec
out.Attr.Ino = child.nodeAttr.Ino
out.Generation = child.nodeAttr.Gen
out.NodeId = child.nodeAttr.Ino
child.setEntryOut(&out.EntryOut)
b.setEntryOutTimeout(&out.EntryOut)
out.Mode = (out.Attr.Mode & 07777) | child.nodeAttr.Mode
return fuse.OK
}
......@@ -342,31 +374,47 @@ func (b *rawBridge) SetDebug(debug bool) {}
func (b *rawBridge) GetAttr(cancel <-chan struct{}, input *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Status {
n, fEntry := b.inode(input.NodeId, input.Fh())
f := fEntry.file
if f == nil {
// The linux kernel doesnt pass along the file
// descriptor, so we have to fake it here.
// See https://github.com/libfuse/libfuse/issues/62
b.mu.Lock()
for _, fh := range n.openFiles {
f = b.files[fh].file
b.files[fh].wg.Add(1)
defer b.files[fh].wg.Done()
break
}
b.mu.Unlock()
}
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
if fops, ok := n.ops.(FileOperations); ok {
return errnoToStatus(b.getattr(ctx, n, f, out))
}
f := fEntry.file
if input.Flags()&fuse.FUSE_GETATTR_FH == 0 {
// The linux kernel doesnt pass along the file
// descriptor, so we have to fake it here.
// See https://github.com/libfuse/libfuse/issues/62
b.mu.Lock()
for _, fh := range n.openFiles {
f = b.files[fh].file
b.files[fh].wg.Add(1)
defer b.files[fh].wg.Done()
break
}
b.mu.Unlock()
}
func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fuse.AttrOut) syscall.Errno {
var errno syscall.Errno
errno := fops.Fgetattr(ctx, f, out)
b.setAttrTimeout(out)
out.Ino = input.NodeId
var fg FileGetattrer
if f != nil {
fg, _ = f.(FileGetattrer)
}
if fops, ok := n.ops.(Getattrer); ok {
errno = fops.Getattr(ctx, f, out)
} else if fg != nil {
errno = fg.Getattr(ctx, out)
} else {
// We set Mode below, which is the minimum for success
}
if errno == 0 {
out.Ino = n.nodeAttr.Ino
out.Mode = (out.Attr.Mode & 07777) | n.nodeAttr.Mode
return errnoToStatus(errno)
b.setAttr(&out.Attr)
b.setAttrTimeout(out)
}
return errnoToStatus(n.ops.Getattr(ctx, out))
return errno
}
func (b *rawBridge) SetAttr(cancel <-chan struct{}, in *fuse.SetAttrIn, out *fuse.AttrOut) fuse.Status {
......@@ -378,18 +426,21 @@ func (b *rawBridge) SetAttr(cancel <-chan struct{}, in *fuse.SetAttrIn, out *fus
f = nil
}
if fops, ok := n.ops.(FileOperations); ok {
return errnoToStatus(fops.Fsetattr(ctx, f, in, out))
if fops, ok := n.ops.(Setattrer); ok {
return errnoToStatus(fops.Setattr(ctx, f, in, out))
}
if fops, ok := f.(FileSetattrer); ok {
return errnoToStatus(fops.Setattr(ctx, in, out))
}
return errnoToStatus(n.ops.Setattr(ctx, in, out))
return fuse.ENOTSUP
}
func (b *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName string, newName string) fuse.Status {
p1, _ := b.inode(input.NodeId, 0)
p2, _ := b.inode(input.Newdir, 0)
if mops, ok := p1.ops.(MutableDirOperations); ok {
if mops, ok := p1.ops.(Renamer); ok {
errno := mops.Rename(&fuse.Context{Caller: input.Caller, Cancel: cancel}, oldName, p2.ops, newName, input.Flags)
if errno == 0 {
if input.Flags&RENAME_EXCHANGE != 0 {
......@@ -408,12 +459,13 @@ func (b *rawBridge) Link(cancel <-chan struct{}, input *fuse.LinkIn, name string
parent, _ := b.inode(input.NodeId, 0)
target, _ := b.inode(input.Oldnodeid, 0)
if mops, ok := parent.ops.(MutableDirOperations); ok {
if mops, ok := parent.ops.(Linker); ok {
child, errno := mops.Link(&fuse.Context{Caller: input.Caller, Cancel: cancel}, target.ops, name, out)
if errno != 0 {
return errnoToStatus(errno)
}
child.setEntryOut(out)
b.addNewChild(parent, name, child, nil, 0, out)
b.setEntryOutTimeout(out)
return fuse.OK
......@@ -424,13 +476,14 @@ func (b *rawBridge) Link(cancel <-chan struct{}, input *fuse.LinkIn, name string
func (b *rawBridge) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target string, name string, out *fuse.EntryOut) fuse.Status {
parent, _ := b.inode(header.NodeId, 0)
if mops, ok := parent.ops.(MutableDirOperations); ok {
if mops, ok := parent.ops.(Symlinker); ok {
child, status := mops.Symlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, target, name, out)
if status != 0 {
return errnoToStatus(status)
}
b.addNewChild(parent, name, child, nil, 0, out)
child.setEntryOut(out)
b.setEntryOutTimeout(out)
return fuse.OK
}
......@@ -439,17 +492,40 @@ func (b *rawBridge) Symlink(cancel <-chan struct{}, header *fuse.InHeader, targe
func (b *rawBridge) Readlink(cancel <-chan struct{}, header *fuse.InHeader) (out []byte, status fuse.Status) {
n, _ := b.inode(header.NodeId, 0)
result, errno := n.linkOps().Readlink(&fuse.Context{Caller: header.Caller, Cancel: cancel})
if errno != 0 {
return nil, errnoToStatus(errno)
if linker, ok := n.ops.(Readlinker); ok {
result, errno := linker.Readlink(&fuse.Context{Caller: header.Caller, Cancel: cancel})
if errno != 0 {
return nil, errnoToStatus(errno)
}
return result, fuse.OK
}
return result, fuse.OK
return nil, fuse.ENOTSUP
}
func (b *rawBridge) Access(cancel <-chan struct{}, input *fuse.AccessIn) fuse.Status {
n, _ := b.inode(input.NodeId, 0)
return errnoToStatus(n.ops.Access(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Mask))
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
if a, ok := n.ops.(Accesser); ok {
return errnoToStatus(a.Access(ctx, input.Mask))
}
// default: check attributes.
caller := input.Caller
var out fuse.AttrOut
if s := b.getattr(ctx, n, nil, &out); s != 0 {
return errnoToStatus(s)
}
if !internal.HasAccess(caller.Uid, caller.Gid, out.Uid, out.Gid, out.Mode, input.Mask) {
return fuse.EACCES
}
return fuse.OK
}
// Extended attributes.
......@@ -457,53 +533,58 @@ func (b *rawBridge) Access(cancel <-chan struct{}, input *fuse.AccessIn) fuse.St
func (b *rawBridge) GetXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string, data []byte) (uint32, fuse.Status) {
n, _ := b.inode(header.NodeId, 0)
if xops, ok := n.ops.(XAttrOperations); ok {
if xops, ok := n.ops.(Getxattrer); ok {
nb, errno := xops.Getxattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, attr, data)
return nb, errnoToStatus(errno)
}
return 0, fuse.ENOTSUP
return 0, fuse.ENOATTR
}
func (b *rawBridge) ListXAttr(cancel <-chan struct{}, header *fuse.InHeader, dest []byte) (sz uint32, status fuse.Status) {
n, _ := b.inode(header.NodeId, 0)
if xops, ok := n.ops.(XAttrOperations); ok {
if xops, ok := n.ops.(Listxattrer); ok {
sz, errno := xops.Listxattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, dest)
return sz, errnoToStatus(errno)
}
return 0, fuse.ENOTSUP
return 0, fuse.OK
}
func (b *rawBridge) SetXAttr(cancel <-chan struct{}, input *fuse.SetXAttrIn, attr string, data []byte) fuse.Status {
n, _ := b.inode(input.NodeId, 0)
if xops, ok := n.ops.(XAttrOperations); ok {
if xops, ok := n.ops.(Setxattrer); ok {
return errnoToStatus(xops.Setxattr(&fuse.Context{Caller: input.Caller, Cancel: cancel}, attr, data, input.Flags))
}
return fuse.ENOTSUP
return fuse.ENOATTR
}
func (b *rawBridge) RemoveXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string) fuse.Status {
n, _ := b.inode(header.NodeId, 0)
if xops, ok := n.ops.(XAttrOperations); ok {
if xops, ok := n.ops.(Removexattrer); ok {
return errnoToStatus(xops.Removexattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, attr))
}
return fuse.ENOTSUP
return fuse.ENOATTR
}
func (b *rawBridge) Open(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status {
n, _ := b.inode(input.NodeId, 0)
f, flags, errno := n.fileOps().Open(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Flags)
if errno != 0 {
return errnoToStatus(errno)
}
if f != nil {
b.mu.Lock()
defer b.mu.Unlock()
out.Fh = uint64(b.registerFile(n, f, input.Flags))
if op, ok := n.ops.(Opener); ok {
f, flags, errno := op.Open(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Flags)
if errno != 0 {
return errnoToStatus(errno)
}
if f != nil {
b.mu.Lock()
defer b.mu.Unlock()
out.Fh = uint64(b.registerFile(n, f, input.Flags))
}
out.OpenFlags = flags
return fuse.OK
}
out.OpenFlags = flags
return fuse.OK
return fuse.ENOTSUP
}
// registerFile hands out a file handle. Must have bridge.mu
......@@ -528,31 +609,49 @@ func (b *rawBridge) registerFile(n *Inode, f FileHandle, flags uint32) uint32 {
func (b *rawBridge) Read(cancel <-chan struct{}, input *fuse.ReadIn, buf []byte) (fuse.ReadResult, fuse.Status) {
n, f := b.inode(input.NodeId, input.Fh)
res, errno := n.fileOps().Read(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, buf, int64(input.Offset))
return res, errnoToStatus(errno)
if fops, ok := n.ops.(Reader); ok {
res, errno := fops.Read(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, buf, int64(input.Offset))
return res, errnoToStatus(errno)
}
if fr, ok := f.file.(FileReader); ok {
res, errno := fr.Read(&fuse.Context{Caller: input.Caller, Cancel: cancel}, buf, int64(input.Offset))
return res, errnoToStatus(errno)
}
return nil, fuse.ENOTSUP
}
func (b *rawBridge) GetLk(cancel <-chan struct{}, input *fuse.LkIn, out *fuse.LkOut) fuse.Status {
n, f := b.inode(input.NodeId, input.Fh)
if lops, ok := n.ops.(LockOperations); ok {
if lops, ok := n.ops.(Getlker); ok {
return errnoToStatus(lops.Getlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags, &out.Lk))
}
if gl, ok := f.file.(FileGetlker); ok {
return errnoToStatus(gl.Getlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags, &out.Lk))
}
return fuse.ENOTSUP
}
func (b *rawBridge) SetLk(cancel <-chan struct{}, input *fuse.LkIn) fuse.Status {
n, f := b.inode(input.NodeId, input.Fh)
if lops, ok := n.ops.(LockOperations); ok {
if lops, ok := n.ops.(Setlker); ok {
return errnoToStatus(lops.Setlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags))
}
if sl, ok := n.ops.(FileSetlker); ok {
return errnoToStatus(sl.Setlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags))
}
return fuse.ENOTSUP
}
func (b *rawBridge) SetLkw(cancel <-chan struct{}, input *fuse.LkIn) fuse.Status {
n, f := b.inode(input.NodeId, input.Fh)
if lops, ok := n.ops.(LockOperations); ok {
if lops, ok := n.ops.(Setlkwer); ok {
return errnoToStatus(lops.Setlkw(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags))
}
if sl, ok := n.ops.(FileSetlkwer); ok {
return errnoToStatus(sl.Setlkw(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags))
}
return fuse.ENOTSUP
}
......@@ -563,7 +662,12 @@ func (b *rawBridge) Release(cancel <-chan struct{}, input *fuse.ReleaseIn) {
}
f.wg.Wait()
n.fileOps().Release(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file)
if r, ok := n.ops.(Releaser); ok {
r.Release(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file)
} else if r, ok := f.file.(FileReleaser); ok {
r.Release(&fuse.Context{Caller: input.Caller, Cancel: cancel})
}
b.mu.Lock()
defer b.mu.Unlock()
......@@ -604,44 +708,75 @@ func (b *rawBridge) releaseFileEntry(nid uint64, fh uint64) (*Inode, *fileEntry)
func (b *rawBridge) Write(cancel <-chan struct{}, input *fuse.WriteIn, data []byte) (written uint32, status fuse.Status) {
n, f := b.inode(input.NodeId, input.Fh)
w, errno := n.fileOps().Write(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, data, int64(input.Offset))
return w, errnoToStatus(errno)
if wr, ok := n.ops.(Writer); ok {
w, errno := wr.Write(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, data, int64(input.Offset))
return w, errnoToStatus(errno)
}
if fr, ok := f.file.(FileWriter); ok {
w, errno := fr.Write(&fuse.Context{Caller: input.Caller, Cancel: cancel}, data, int64(input.Offset))
return w, errnoToStatus(errno)
}
return 0, fuse.ENOTSUP
}
func (b *rawBridge) Flush(cancel <-chan struct{}, input *fuse.FlushIn) fuse.Status {
n, f := b.inode(input.NodeId, input.Fh)
return errnoToStatus(n.fileOps().Flush(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file))
if fl, ok := n.ops.(Flusher); ok {
return errnoToStatus(fl.Flush(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file))
}
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
}
func (b *rawBridge) Fsync(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status {
n, f := b.inode(input.NodeId, input.Fh)
return errnoToStatus(n.fileOps().Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.FsyncFlags))
if fs, ok := n.ops.(Fsyncer); ok {
return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.FsyncFlags))
}
if fs, ok := f.file.(FileFsyncer); ok {
return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.FsyncFlags))
}
return fuse.ENOTSUP
}
func (b *rawBridge) Fallocate(cancel <-chan struct{}, input *fuse.FallocateIn) fuse.Status {
n, f := b.inode(input.NodeId, input.Fh)
return errnoToStatus(n.fileOps().Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Offset, input.Length, input.Mode))
if a, ok := n.ops.(Allocater); ok {
return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Offset, input.Length, input.Mode))
}
if a, ok := n.ops.(FileAllocater); ok {
return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Offset, input.Length, input.Mode))
}
return fuse.ENOTSUP
}
func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status {
n, _ := b.inode(input.NodeId, 0)
errno := n.dirOps().Opendir(&fuse.Context{Caller: input.Caller, Cancel: cancel})
if errno != 0 {
return errnoToStatus(errno)
if od, ok := n.ops.(Opendirer); ok {
errno := od.Opendir(&fuse.Context{Caller: input.Caller, Cancel: cancel})
if errno != 0 {
return errnoToStatus(errno)
}
}
b.mu.Lock()
defer b.mu.Unlock()
out.Fh = uint64(b.registerFile(n, nil, 0))
return fuse.OK
}
func (b *rawBridge) getStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) syscall.Errno {
func (b *rawBridge) setStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) syscall.Errno {
if f.dirStream == nil || input.Offset == 0 {
if f.dirStream != nil {
f.dirStream.Close()
f.dirStream = nil
}
str, errno := inode.dirOps().Readdir(&fuse.Context{Caller: input.Caller, Cancel: cancel})
str, errno := b.getStream(&fuse.Context{Caller: input.Caller, Cancel: cancel}, inode)
if errno != 0 {
return errno
}
......@@ -653,10 +788,25 @@ func (b *rawBridge) getStream(cancel <-chan struct{}, input *fuse.ReadIn, inode
return 0
}
func (b *rawBridge) getStream(ctx context.Context, inode *Inode) (DirStream, syscall.Errno) {
rd, ok := inode.ops.(Readdirer)
if !ok {
r := []fuse.DirEntry{}
for k, ch := range inode.Children() {
r = append(r, fuse.DirEntry{Mode: ch.Mode(),
Name: k,
Ino: ch.NodeAttr().Ino})
}
return NewListDirStream(r), 0
}
return rd.Readdir(ctx)
}
func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status {
n, f := b.inode(input.NodeId, input.Fh)
if errno := b.getStream(cancel, input, n, f); errno != 0 {
if errno := b.setStream(cancel, input, n, f); errno != 0 {
return errnoToStatus(errno)
}
......@@ -685,10 +835,10 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus
func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status {
n, f := b.inode(input.NodeId, input.Fh)
if errno := b.getStream(cancel, input, n, f); errno != 0 {
if errno := b.setStream(cancel, input, n, f); errno != 0 {
return errnoToStatus(errno)
}
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
for f.dirStream.HasNext() {
var e fuse.DirEntry
var errno syscall.Errno
......@@ -710,13 +860,14 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
return fuse.OK
}
child, errno := n.dirOps().Lookup(&fuse.Context{Caller: input.Caller, Cancel: cancel}, e.Name, entryOut)
child, errno := b.lookup(ctx, n, e.Name, entryOut)
if errno != 0 {
if b.options.NegativeTimeout != nil {
entryOut.SetEntryTimeout(*b.options.NegativeTimeout)
}
} else {
b.addNewChild(n, e.Name, child, nil, 0, entryOut)
child.setEntryOut(entryOut)
b.setEntryOutTimeout(entryOut)
if (e.Mode &^ 07777) != (child.nodeAttr.Mode &^ 07777) {
// should go back and change the
......@@ -732,12 +883,21 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
func (b *rawBridge) FsyncDir(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status {
n, _ := b.inode(input.NodeId, input.Fh)
return errnoToStatus(n.fileOps().Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, nil, input.FsyncFlags))
if fs, ok := n.ops.(Fsyncer); ok {
return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, nil, input.FsyncFlags))
}
return fuse.ENOTSUP
}
func (b *rawBridge) StatFs(cancel <-chan struct{}, input *fuse.InHeader, out *fuse.StatfsOut) fuse.Status {
n, _ := b.inode(input.NodeId, 0)
return errnoToStatus(n.ops.Statfs(&fuse.Context{Caller: input.Caller, Cancel: cancel}, out))
if sf, ok := n.ops.(Statfser); ok {
return errnoToStatus(sf.Statfs(&fuse.Context{Caller: input.Caller, Cancel: cancel}, out))
}
// leave zeroed out
return fuse.OK
}
func (b *rawBridge) Init(s *fuse.Server) {
......@@ -746,9 +906,14 @@ func (b *rawBridge) Init(s *fuse.Server) {
func (b *rawBridge) CopyFileRange(cancel <-chan struct{}, in *fuse.CopyFileRangeIn) (size uint32, status fuse.Status) {
n1, f1 := b.inode(in.NodeId, in.FhIn)
cfr, ok := n1.ops.(CopyFileRanger)
if !ok {
return 0, fuse.ENOTSUP
}
n2, f2 := b.inode(in.NodeIdOut, in.FhOut)
sz, errno := n1.fileOps().CopyFileRange(&fuse.Context{Caller: in.Caller, Cancel: cancel},
sz, errno := cfr.CopyFileRange(&fuse.Context{Caller: in.Caller, Cancel: cancel},
f1.file, in.OffIn, n2, f2.file, in.OffOut, in.Len, in.Flags)
return sz, errnoToStatus(errno)
}
......@@ -756,8 +921,23 @@ func (b *rawBridge) CopyFileRange(cancel <-chan struct{}, in *fuse.CopyFileRange
func (b *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekOut) fuse.Status {
n, f := b.inode(in.NodeId, in.Fh)
off, errno := n.fileOps().Lseek(&fuse.Context{Caller: in.Caller, Cancel: cancel},
f.file, in.Offset, in.Whence)
out.Offset = off
return errnoToStatus(errno)
ls, ok := n.ops.(Lseeker)
if ok {
off, errno := ls.Lseek(&fuse.Context{Caller: in.Caller, Cancel: cancel},
f.file, in.Offset, in.Whence)
out.Offset = off
return errnoToStatus(errno)
}
if fs, ok := f.file.(FileLseeker); ok {
off, errno := fs.Lseek(&fuse.Context{Caller: in.Caller, Cancel: cancel}, in.Offset, in.Whence)
out.Offset = off
return errnoToStatus(errno)
}
if in.Whence == _SEEK_DATA || in.Whence == _SEEK_HOLE {
out.Offset = in.Offset
return fuse.OK
}
return fuse.ENOTSUP
}
......@@ -9,17 +9,15 @@ import (
"context"
"fmt"
"io/ioutil"
"os"
"sync"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
)
type keepCacheFile struct {
OperationStubs
Inode
keepCache bool
mu sync.Mutex
......@@ -27,6 +25,10 @@ type keepCacheFile struct {
count int
}
var _ = (Reader)((*keepCacheFile)(nil))
var _ = (Opener)((*keepCacheFile)(nil))
var _ = (Getattrer)((*keepCacheFile)(nil))
func (f *keepCacheFile) setContent(delta int) {
f.mu.Lock()
defer f.mu.Unlock()
......@@ -44,7 +46,7 @@ func (f *keepCacheFile) Open(ctx context.Context, flags uint32) (FileHandle, uin
return nil, fl, OK
}
func (f *keepCacheFile) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno {
func (f *keepCacheFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
out.Size = uint64(len(f.content))
......@@ -62,13 +64,15 @@ func (f *keepCacheFile) Read(ctx context.Context, fh FileHandle, dest []byte, of
}
type keepCacheRoot struct {
OperationStubs
Inode
keep, nokeep *keepCacheFile
}
var _ = (OnAdder)((*keepCacheRoot)(nil))
func (r *keepCacheRoot) OnAdd(ctx context.Context) {
i := r.Inode()
i := &r.Inode
r.keep = &keepCacheFile{
keepCache: true,
......@@ -87,18 +91,9 @@ func (r *keepCacheRoot) OnAdd(ctx context.Context) {
// invalidation triggers if mtime or file size is changed, so only
// change content but no metadata.
func TestKeepCache(t *testing.T) {
mntDir := testutil.TempDir()
defer os.RemoveAll(mntDir)
root := &keepCacheRoot{}
server, err := Mount(mntDir, root, &Options{
MountOptions: fuse.MountOptions{
Debug: testutil.VerboseTest(),
},
FirstAutomaticIno: 1,
// no caching.
})
defer server.Unmount()
mntDir, clean := testMount(t, root, nil)
defer clean()
c1, err := ioutil.ReadFile(mntDir + "/keep")
if err != nil {
t.Fatalf("read keep 1: %v", err)
......@@ -113,7 +108,7 @@ func TestKeepCache(t *testing.T) {
t.Errorf("keep read 2 got %q want read 1 %q", c2, c1)
}
if s := root.keep.Inode().NotifyContent(0, 100); s != OK {
if s := root.keep.Inode.NotifyContent(0, 100); s != OK {
t.Errorf("NotifyContent: %v", s)
}
......
......@@ -3,371 +3,3 @@
// license that can be found in the LICENSE file.
package nodefs
import (
"context"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal"
)
// InodeEmbed embeds the Inode into a filesystem node. It is the only
// type that implements the InodeLink interface, and hence, it must be
// part of any implementation of Operations.
type InodeEmbed struct {
inode_ Inode
}
var _ = (InodeLink)((*InodeEmbed)(nil))
func (n *InodeEmbed) inode() *Inode {
return &n.inode_
}
func (n *InodeEmbed) init(ops Operations, attr NodeAttr, bridge *rawBridge, persistent bool) {
n.inode_ = Inode{
ops: ops,
nodeAttr: attr,
bridge: bridge,
persistent: persistent,
parents: make(map[parentData]struct{}),
}
if attr.Mode == fuse.S_IFDIR {
n.inode_.children = make(map[string]*Inode)
}
}
// Inode returns the Inode for this Operations
func (n *InodeEmbed) Inode() *Inode {
return &n.inode_
}
// OperationStubs provides no-operation default implementations for
// all the XxxOperations interfaces. The stubs provide useful defaults
// for implementing a read-only filesystem whose tree is constructed
// beforehand in the OnAdd method of the root. A example is in
// zip_test.go
//
// It is recommended to embed this in any Operations implementation,
// as it is the means by new operations are supported.
type OperationStubs struct {
InodeEmbed
}
// check that we have implemented all interface methods
var _ DirOperations = &OperationStubs{}
var _ FileOperations = &OperationStubs{}
var _ LockOperations = &OperationStubs{}
// StatFs zeroes the out argument and returns OK. This is because OSX
// filesystems must define this, or the mount will not work.
func (n *OperationStubs) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
// this should be defined on OSX, or the FS won't mount
*out = fuse.StatfsOut{}
return OK
}
// The default OnAdd does nothing.
func (n *OperationStubs) OnAdd(ctx context.Context) {
}
// GetAttr zeroes out argument and returns OK.
func (n *OperationStubs) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno {
*out = fuse.AttrOut{}
return OK
}
func (n *OperationStubs) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
return syscall.EROFS
}
// The Access default implementation checks traditional unix
// permissions of the GetAttr result agains the caller.
func (n *OperationStubs) Access(ctx context.Context, mask uint32) syscall.Errno {
caller, ok := fuse.FromContext(ctx)
if !ok {
return syscall.EINVAL
}
var out fuse.AttrOut
if s := n.inode().Operations().Getattr(ctx, &out); s != 0 {
return s
}
if !internal.HasAccess(caller.Uid, caller.Gid, out.Uid, out.Gid, out.Mode, mask) {
return syscall.EACCES
}
return OK
}
// FSetAttr delegates to the FileHandle's if f is not nil, or else to the
// Inode's SetAttr method.
func (n *OperationStubs) Fsetattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
if f != nil {
return f.Setattr(ctx, in, out)
}
return n.inode_.Operations().Setattr(ctx, in, out)
}
// The Lookup method on the OperationStubs type looks for an
// existing child with the given name, or returns ENOENT.
func (n *OperationStubs) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
ch := n.inode().GetChild(name)
if ch == nil {
return nil, syscall.ENOENT
}
var a fuse.AttrOut
errno := ch.Operations().Getattr(ctx, &a)
out.Attr = a.Attr
return ch, errno
}
// Mkdir returns EROFS
func (n *OperationStubs) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
return nil, syscall.EROFS
}
// Mknod returns EROFS
func (n *OperationStubs) Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
return nil, syscall.EROFS
}
// Rmdir returns EROFS
func (n *OperationStubs) Rmdir(ctx context.Context, name string) syscall.Errno {
return syscall.EROFS
}
// Unlink returns EROFS
func (n *OperationStubs) Unlink(ctx context.Context, name string) syscall.Errno {
return syscall.EROFS
}
// The default OpenDir always succeeds
func (n *OperationStubs) Opendir(ctx context.Context) syscall.Errno {
return OK
}
// The default ReadDir returns the list of children from the tree
func (n *OperationStubs) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
r := []fuse.DirEntry{}
for k, ch := range n.inode().Children() {
r = append(r, fuse.DirEntry{Mode: ch.Mode(),
Name: k,
Ino: ch.NodeAttr().Ino})
}
return NewListDirStream(r), 0
}
// Rename returns EROFS
func (n *OperationStubs) Rename(ctx context.Context, name string, newParent Operations, newName string, flags uint32) syscall.Errno {
return syscall.EROFS
}
// Read delegates to the FileHandle argument.
func (n *OperationStubs) Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
if f != nil {
return f.Read(ctx, dest, off)
}
return nil, syscall.ENOTSUP
}
// Symlink returns EROFS
func (n *OperationStubs) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno) {
return nil, syscall.EROFS
}
// Readlink return ENOTSUP
func (n *OperationStubs) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
return nil, syscall.ENOTSUP
}
// Fsync delegates to the FileHandle
func (n *OperationStubs) Fsync(ctx context.Context, f FileHandle, flags uint32) syscall.Errno {
if f != nil {
return f.Fsync(ctx, flags)
}
return syscall.ENOTSUP
}
// Write delegates to the FileHandle
func (n *OperationStubs) Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, errno syscall.Errno) {
if f != nil {
return f.Write(ctx, data, off)
}
return 0, syscall.EROFS
}
func (n *OperationStubs) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
return 0, syscall.EROFS
}
// Lseek is called for seeking to and beyond holes. By default, it
// returns the input offset unchanged.
func (n *OperationStubs) Lseek(ctx context.Context, f FileHandle, off uint64, whence uint32) (uint64, syscall.Errno) {
if f != nil {
return f.Lseek(ctx, off, whence)
}
if whence == _SEEK_DATA || whence == _SEEK_HOLE {
return off, OK
}
return 0, syscall.ENOTSUP
}
// Getlk delegates to the FileHandlef
func (n *OperationStubs) Getlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (errno syscall.Errno) {
if f != nil {
return f.Getlk(ctx, owner, lk, flags, out)
}
return syscall.ENOTSUP
}
// SetLk delegates to the FileHandle
func (n *OperationStubs) Setlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) {
if f != nil {
return f.Setlk(ctx, owner, lk, flags)
}
return syscall.ENOTSUP
}
// SetLkw delegates to the FileHandle
func (n *OperationStubs) Setlkw(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) {
if f != nil {
return f.Setlkw(ctx, owner, lk, flags)
}
return syscall.ENOTSUP
}
// Flush delegates to the FileHandle
func (n *OperationStubs) Flush(ctx context.Context, f FileHandle) syscall.Errno {
if f != nil {
return f.Flush(ctx)
}
return syscall.ENOTSUP
}
// Release delegates to the FileHandle
func (n *OperationStubs) Release(ctx context.Context, f FileHandle) syscall.Errno {
if f != nil {
return f.Release(ctx)
}
return OK
}
// Allocate delegates to the FileHandle
func (n *OperationStubs) Allocate(ctx context.Context, f FileHandle, off uint64, size uint64, mode uint32) (errno syscall.Errno) {
if f != nil {
return f.Allocate(ctx, off, size, mode)
}
return syscall.ENOTSUP
}
// Fgetattr delegates to the FileHandle's if f is not nil, or else to the
// Inode's GetAttr method.
func (n *OperationStubs) Fgetattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
if f != nil {
f.Getattr(ctx, out)
}
return n.inode_.ops.Getattr(ctx, out)
}
// Open returns ENOTSUP
func (n *OperationStubs) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
return nil, 0, syscall.ENOTSUP
}
// Create returns ENOTSUP
func (n *OperationStubs) Create(ctx context.Context, name string, flags uint32, mode uint32) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
return nil, nil, 0, syscall.EROFS
}
// Link returns ENOTSUP
func (n *OperationStubs) Link(ctx context.Context, target Operations, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno) {
return nil, syscall.EROFS
}
// The default GetXAttr returns ENOATTR
func (n *OperationStubs) GetXAttr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
return 0, ENOATTR
}
// The default SetXAttr returns ENOATTR
func (n *OperationStubs) SetXAttr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
return syscall.EROFS
}
// The default RemoveXAttr returns ENOATTR
func (n *OperationStubs) RemoveXAttr(ctx context.Context, attr string) syscall.Errno {
return ENOATTR
}
// The default RemoveXAttr returns an empty list
func (n *OperationStubs) ListXAttr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
return 0, OK
}
// FileHandleStubs satisfies the FileHandle interface, and provides
// stub methods that return ENOTSUP for all operations.
type FileHandleStubs struct {
}
var _ = FileHandle((*FileHandleStubs)(nil))
func (f *FileHandleStubs) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
return nil, syscall.ENOTSUP
}
func (f *FileHandleStubs) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) {
return 0, syscall.ENOTSUP
}
func (f *FileHandleStubs) Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (errno syscall.Errno) {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Flush(ctx context.Context) syscall.Errno {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Release(ctx context.Context) syscall.Errno {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Allocate(ctx context.Context, off uint64, size uint64, mode uint32) (errno syscall.Errno) {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
return syscall.ENOTSUP
}
func (f *FileHandleStubs) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) {
return 0, syscall.ENOTSUP
}
......@@ -13,23 +13,24 @@ import (
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
)
type dioRoot struct {
OperationStubs
Inode
}
func (r *dioRoot) OnAdd(ctx context.Context) {
r.Inode().AddChild("file", r.Inode().NewInode(ctx, &dioFile{}, NodeAttr{}), false)
r.Inode.AddChild("file", r.Inode.NewInode(ctx, &dioFile{}, NodeAttr{}), false)
}
// A file handle that pretends that every hole/data starts at
// multiples of 1024
type dioFH struct {
FileHandleStubs
}
var _ = (FileLseeker)((*dioFH)(nil))
var _ = (FileReader)((*dioFH)(nil))
func (f *dioFH) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) {
next := (off + 1023) & (^uint64(1023))
return next, OK
......@@ -42,27 +43,19 @@ func (fh *dioFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadRes
// overrides Open so it can return a dioFH file handle
type dioFile struct {
OperationStubs
Inode
}
var _ = (Opener)((*dioFile)(nil))
func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
return &dioFH{}, fuse.FOPEN_DIRECT_IO, OK
}
func TestDirectIO(t *testing.T) {
root := &dioRoot{}
mntDir := testutil.TempDir()
defer os.RemoveAll(mntDir)
server, err := Mount(mntDir, root, &Options{
MountOptions: fuse.MountOptions{
Debug: testutil.VerboseTest(),
},
FirstAutomaticIno: 1,
// no caching.
})
defer server.Unmount()
mntDir, clean := testMount(t, root, nil)
defer clean()
f, err := os.Open(mntDir + "/file")
if err != nil {
......
......@@ -24,6 +24,20 @@ type loopbackFile struct {
fd int
}
var _ = (FileHandle)((*loopbackFile)(nil))
var _ = (FileReleaser)((*loopbackFile)(nil))
var _ = (FileGetattrer)((*loopbackFile)(nil))
var _ = (FileReader)((*loopbackFile)(nil))
var _ = (FileWriter)((*loopbackFile)(nil))
var _ = (FileGetlker)((*loopbackFile)(nil))
var _ = (FileSetlker)((*loopbackFile)(nil))
var _ = (FileSetlkwer)((*loopbackFile)(nil))
var _ = (FileLseeker)((*loopbackFile)(nil))
var _ = (FileFlusher)((*loopbackFile)(nil))
var _ = (FileFsyncer)((*loopbackFile)(nil))
var _ = (FileSetattrer)((*loopbackFile)(nil))
var _ = (FileAllocater)((*loopbackFile)(nil))
func (f *loopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
r := fuse.ReadResultFd(uintptr(f.fd), off, len(buf))
return r, OK
......
......@@ -13,6 +13,8 @@ import (
"sync"
"syscall"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
)
type parentData struct {
......@@ -50,10 +52,14 @@ func (i *NodeAttr) Reserved() bool {
// Operations instances, which is the extension interface for file
// systems. One can create fully-formed trees of Inodes ahead of time
// by creating "persistent" Inodes.
//
// The Inode struct contains a lock, so it should not be
// copied. Inodes should be obtained by calling Inode.NewInode() or
// Inode.NewPersistentInode().
type Inode struct {
nodeAttr NodeAttr
ops Operations
ops InodeEmbedder
bridge *rawBridge
// Following data is mutable.
......@@ -90,16 +96,30 @@ type Inode struct {
parents map[parentData]struct{}
}
func (n *Inode) dirOps() DirOperations {
return n.ops.(DirOperations)
func (n *Inode) embed() *Inode {
return n
}
func (n *Inode) fileOps() FileOperations {
return n.ops.(FileOperations)
func (n *Inode) EmbeddedInode() *Inode {
return n
}
func initInode(n *Inode, ops InodeEmbedder, attr NodeAttr, bridge *rawBridge, persistent bool) {
n.ops = ops
n.nodeAttr = attr
n.bridge = bridge
n.persistent = persistent
n.parents = make(map[parentData]struct{})
if attr.Mode == fuse.S_IFDIR {
n.children = make(map[string]*Inode)
}
}
func (n *Inode) linkOps() SymlinkOperations {
return n.ops.(SymlinkOperations)
// Set node ID and mode in EntryOut
func (n *Inode) setEntryOut(out *fuse.EntryOut) {
out.NodeId = n.nodeAttr.Ino
out.Ino = n.nodeAttr.Ino
out.Mode = (out.Attr.Mode & 07777) | n.nodeAttr.Mode
}
// NodeAttr returns the (Ino, Gen) tuple for this node.
......@@ -117,14 +137,28 @@ func (n *Inode) Root() *Inode {
return n.bridge.root
}
func modeStr(m uint32) string {
return map[uint32]string{
syscall.S_IFREG: "reg",
syscall.S_IFLNK: "lnk",
syscall.S_IFDIR: "dir",
syscall.S_IFSOCK: "soc",
syscall.S_IFIFO: "pip",
syscall.S_IFCHR: "chr",
syscall.S_IFBLK: "blk",
}[m]
}
// debugString is used for debugging. Racy.
func (n *Inode) debugString() string {
func (n *Inode) String() string {
n.mu.Lock()
defer n.mu.Unlock()
var ss []string
for nm, ch := range n.children {
ss = append(ss, fmt.Sprintf("%q=%d", nm, ch.nodeAttr.Ino))
ss = append(ss, fmt.Sprintf("%q=%d[%s]", nm, ch.nodeAttr.Ino, modeStr(ch.nodeAttr.Mode)))
}
return fmt.Sprintf("%d: %s", n.nodeAttr, strings.Join(ss, ","))
return fmt.Sprintf("%d[%s]: %s", n.nodeAttr.Ino, modeStr(n.nodeAttr.Mode), strings.Join(ss, ","))
}
// sortNodes rearranges inode group in consistent order.
......@@ -214,7 +248,7 @@ func (n *Inode) Forgotten() bool {
// Operations returns the object implementing the file system
// operations.
func (n *Inode) Operations() Operations {
func (n *Inode) Operations() InodeEmbedder {
return n.ops
}
......@@ -273,7 +307,7 @@ func (iparent *Inode) setEntry(name string, ichild *Inode) {
// NewPersistentInode returns an Inode whose lifetime is not in
// control of the kernel.
func (n *Inode) NewPersistentInode(ctx context.Context, node Operations, id NodeAttr) *Inode {
func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id NodeAttr) *Inode {
return n.newInode(ctx, node, id, true)
}
......@@ -284,16 +318,16 @@ func (n *Inode) ForgetPersistent() {
n.removeRef(0, true)
}
// NewInode returns an inode for the given Operations. The mode should
// be standard mode argument (eg. S_IFDIR). The inode number in id.Ino
// argument is used to implement hard-links. If it is given, and
// another node with the same ID is known, that will node will be
// NewInode returns an inode for the given InodeEmbedder. The mode
// should be standard mode argument (eg. S_IFDIR). The inode number in
// id.Ino argument is used to implement hard-links. If it is given,
// and another node with the same ID is known, that will node will be
// returned, and the passed-in `node` is ignored.
func (n *Inode) NewInode(ctx context.Context, ops Operations, id NodeAttr) *Inode {
return n.newInode(ctx, ops, id, false)
func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id NodeAttr) *Inode {
return n.newInode(ctx, node, id, false)
}
func (n *Inode) newInode(ctx context.Context, ops Operations, id NodeAttr, persistent bool) *Inode {
func (n *Inode) newInode(ctx context.Context, ops InodeEmbedder, id NodeAttr, persistent bool) *Inode {
return n.bridge.newInode(ctx, ops, id, persistent)
}
......@@ -454,6 +488,22 @@ func (n *Inode) Parent() (string, *Inode) {
return "", nil
}
// RmAllChildren recursively drops a tree, forgetting all persistent
// nodes.
func (n *Inode) RmAllChildren() {
for {
chs := n.Children()
if len(chs) == 0 {
break
}
for nm, ch := range chs {
ch.RmAllChildren()
n.RmChild(nm)
}
}
n.removeRef(0, true)
}
// RmChild removes multiple children. Returns whether the removal
// succeeded and whether the node is still live afterward. The removal
// is transactional: it only succeeds if all names are children, and
......
......@@ -6,37 +6,39 @@ package nodefs
import (
"context"
"os"
"os/exec"
"syscall"
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
)
type interruptRoot struct {
OperationStubs
Inode
child interruptOps
}
type interruptOps struct {
OperationStubs
interrupted bool
}
var _ = (Lookuper)((*interruptRoot)(nil))
func (r *interruptRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
if name != "file" {
return nil, syscall.ENOENT
}
ch := r.Inode().NewInode(ctx, &r.child, NodeAttr{
ch := r.Inode.NewInode(ctx, &r.child, NodeAttr{
Ino: 2,
Gen: 1})
return ch, OK
}
type interruptOps struct {
Inode
interrupted bool
}
var _ = (Opener)((*interruptOps)(nil))
func (o *interruptOps) Open(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno) {
select {
case <-time.After(100 * time.Millisecond):
......@@ -50,22 +52,18 @@ func (o *interruptOps) Open(ctx context.Context, flags uint32) (FileHandle, uint
// This currently doesn't test functionality, but is useful to investigate how
// INTERRUPT opcodes are handled.
func TestInterrupt(t *testing.T) {
mntDir := testutil.TempDir()
defer os.Remove(mntDir)
root := &interruptRoot{}
oneSec := time.Second
server, err := Mount(mntDir, root, &Options{
MountOptions: fuse.MountOptions{
Debug: testutil.VerboseTest(),
},
mntDir, clean := testMount(t, root, &Options{
EntryTimeout: &oneSec,
AttrTimeout: &oneSec,
})
if err != nil {
t.Fatal(err)
}
defer server.Unmount()
defer func() {
if clean != nil {
clean()
}
}()
cmd := exec.Command("cat", mntDir+"/file")
if err := cmd.Start(); err != nil {
......@@ -77,7 +75,9 @@ func TestInterrupt(t *testing.T) {
t.Errorf("Kill: %v", err)
}
server.Unmount()
clean()
clean = nil
if !root.child.interrupted {
t.Errorf("open request was not interrupted")
}
......
......@@ -6,7 +6,6 @@ package nodefs
import (
"context"
"log"
"os"
"path/filepath"
"syscall"
......@@ -21,6 +20,32 @@ type loopbackRoot struct {
rootDev uint64
}
type loopbackNode struct {
Inode
}
var _ = (Statfser)((*loopbackNode)(nil))
var _ = (Statfser)((*loopbackNode)(nil))
var _ = (Getattrer)((*loopbackNode)(nil))
var _ = (Getxattrer)((*loopbackNode)(nil))
var _ = (Setxattrer)((*loopbackNode)(nil))
var _ = (Removexattrer)((*loopbackNode)(nil))
var _ = (Listxattrer)((*loopbackNode)(nil))
var _ = (Readlinker)((*loopbackNode)(nil))
var _ = (Opener)((*loopbackNode)(nil))
var _ = (CopyFileRanger)((*loopbackNode)(nil))
var _ = (Lookuper)((*loopbackNode)(nil))
var _ = (Opendirer)((*loopbackNode)(nil))
var _ = (Readdirer)((*loopbackNode)(nil))
var _ = (Mkdirer)((*loopbackNode)(nil))
var _ = (Mknoder)((*loopbackNode)(nil))
var _ = (Linker)((*loopbackNode)(nil))
var _ = (Symlinker)((*loopbackNode)(nil))
var _ = (Creater)((*loopbackNode)(nil))
var _ = (Unlinker)((*loopbackNode)(nil))
var _ = (Rmdirer)((*loopbackNode)(nil))
var _ = (Renamer)((*loopbackNode)(nil))
func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
s := syscall.Statfs_t{}
err := syscall.Statfs(n.path(), &s)
......@@ -31,8 +56,8 @@ func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.
return OK
}
func (n *loopbackRoot) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno {
log.Println("getattr")
func (n *loopbackRoot) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
st := syscall.Stat_t{}
err := syscall.Stat(n.rootPath, &st)
if err != nil {
......@@ -42,16 +67,12 @@ func (n *loopbackRoot) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.E
return OK
}
type loopbackNode struct {
OperationStubs
}
func (n *loopbackNode) root() *loopbackRoot {
return n.Inode().Root().Operations().(*loopbackRoot)
return n.Root().Operations().(*loopbackRoot)
}
func (n *loopbackNode) path() string {
path := n.Inode().Path(nil)
path := n.Path(nil)
return filepath.Join(n.root().rootPath, path)
}
......@@ -66,7 +87,7 @@ func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryO
out.Attr.FromStat(&st)
node := &loopbackNode{}
ch := n.inode().NewInode(ctx, node, n.root().idFromStat(&st))
ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
return ch, 0
}
......@@ -85,7 +106,7 @@ func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32
out.Attr.FromStat(&st)
node := &loopbackNode{}
ch := n.inode().NewInode(ctx, node, n.root().idFromStat(&st))
ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
return ch, 0
}
......@@ -105,7 +126,7 @@ func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out
out.Attr.FromStat(&st)
node := &loopbackNode{}
ch := n.inode().NewInode(ctx, node, n.root().idFromStat(&st))
ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
return ch, 0
}
......@@ -122,14 +143,14 @@ func (n *loopbackNode) Unlink(ctx context.Context, name string) syscall.Errno {
return ToErrno(err)
}
func toLoopbackNode(op Operations) *loopbackNode {
func toLoopbackNode(op InodeEmbedder) *loopbackNode {
if r, ok := op.(*loopbackRoot); ok {
return &r.loopbackNode
}
return op.(*loopbackNode)
}
func (n *loopbackNode) Rename(ctx context.Context, name string, newParent Operations, newName string, flags uint32) syscall.Errno {
func (n *loopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno {
newParentLoopback := toLoopbackNode(newParent)
if flags&RENAME_EXCHANGE != 0 {
return n.renameExchange(name, newParentLoopback, newName)
......@@ -176,7 +197,7 @@ func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mo
}
node := &loopbackNode{}
ch := n.inode().NewInode(ctx, node, n.root().idFromStat(&st))
ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
lf := NewLoopbackFile(fd)
return ch, lf, 0, 0
}
......@@ -193,13 +214,13 @@ func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fu
return nil, ToErrno(err)
}
node := &loopbackNode{}
ch := n.inode().NewInode(ctx, node, n.root().idFromStat(&st))
ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
out.Attr.FromStat(&st)
return ch, 0
}
func (n *loopbackNode) Link(ctx context.Context, target Operations, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
func (n *loopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
p := filepath.Join(n.path(), name)
targetNode := toLoopbackNode(target)
......@@ -213,7 +234,7 @@ func (n *loopbackNode) Link(ctx context.Context, target Operations, name string,
return nil, ToErrno(err)
}
node := &loopbackNode{}
ch := n.inode().NewInode(ctx, node, n.root().idFromStat(&st))
ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
out.Attr.FromStat(&st)
return ch, 0
......@@ -258,11 +279,10 @@ func (n *loopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
return NewLoopbackDirStream(n.path())
}
func (n *loopbackNode) Fgetattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
func (n *loopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
if f != nil {
return f.Getattr(ctx, out)
return f.(FileGetattrer).Getattr(ctx, out)
}
p := n.path()
var err error = nil
......@@ -277,7 +297,7 @@ func (n *loopbackNode) Fgetattr(ctx context.Context, f FileHandle, out *fuse.Att
// NewLoopback returns a root node for a loopback file system whose
// root is at the given root.
func NewLoopbackRoot(root string) (DirOperations, error) {
func NewLoopbackRoot(root string) (InodeEmbedder, error) {
var st syscall.Stat_t
err := syscall.Stat(root, &st)
if err != nil {
......
// +build darwin
// 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.
......@@ -14,19 +16,19 @@ import (
"github.com/hanwen/go-fuse/internal/utimens"
)
func (n *loopbackNode) GetXAttr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
func (n *loopbackNode) SetXAttr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
func (n *loopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
return syscall.ENOSYS
}
func (n *loopbackNode) RemoveXAttr(ctx context.Context, attr string) syscall.Errno {
func (n *loopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
return syscall.ENOSYS
}
func (n *loopbackNode) ListXAttr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
func (n *loopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
......@@ -95,11 +97,11 @@ func timeToTimeval(t *time.Time) syscall.Timeval {
}
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We emulate using utimes() and extra GetAttr() calls.
// We emulate using utimes() and extra Getattr() calls.
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var attr fuse.AttrOut
if a == nil || m == nil {
errno := f.GetAttr(context.Background(), &attr)
errno := f.Getattr(context.Background(), &attr)
if errno != 0 {
return errno
}
......@@ -108,3 +110,9 @@ func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
err := syscall.Futimes(int(f.fd), tv)
return ToErrno(err)
}
func (n *loopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
// +build linux
// 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.
......@@ -49,7 +51,7 @@ func (n *loopbackNode) renameExchange(name string, newparent *loopbackNode, newN
}
// Double check that nodes didn't change from under us.
inode := n.Inode()
inode := &n.Inode
if inode.Root() != inode && inode.NodeAttr().Ino != n.root().idFromStat(&st).Ino {
return syscall.EBUSY
}
......@@ -57,7 +59,7 @@ func (n *loopbackNode) renameExchange(name string, newparent *loopbackNode, newN
return ToErrno(err)
}
newinode := newparent.Inode()
newinode := &newparent.Inode
if newinode.Root() != newinode && newinode.NodeAttr().Ino != n.root().idFromStat(&st).Ino {
return syscall.EBUSY
}
......
// 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 nodefs
import (
"context"
"syscall"
"github.com/hanwen/go-fuse/fuse"
)
// MemRegularFile is a filesystem node that holds a read-only data
// slice in memory.
type MemRegularFile struct {
Inode
Data []byte
Attr fuse.Attr
}
var _ = (Opener)((*MemRegularFile)(nil))
var _ = (Getattrer)((*MemRegularFile)(nil))
var _ = (Reader)((*MemRegularFile)(nil))
var _ = (Flusher)((*MemRegularFile)(nil))
func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
if flags&(syscall.O_RDWR) != 0 || flags&syscall.O_WRONLY != 0 {
return nil, 0, syscall.EPERM
}
return nil, fuse.FOPEN_KEEP_CACHE, OK
}
func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Attr = f.Attr
out.Attr.Size = uint64(len(f.Data))
return OK
}
func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno {
return 0
}
func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := int(off) + len(dest)
if end > len(f.Data) {
end = len(f.Data)
}
return fuse.ReadResultData(f.Data[off:end]), OK
}
// MemSymlink is an inode holding a symlink in memory.
type MemSymlink struct {
Inode
Data []byte
}
var _ = (Readlinker)((*MemSymlink)(nil))
func (l *MemSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
return l.Data, OK
}
// 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 nodefs
import (
"bytes"
"context"
"io/ioutil"
"math/rand"
"os"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
)
func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, func()) {
t.Helper()
mntDir := testutil.TempDir()
if opts == nil {
opts = &Options{
FirstAutomaticIno: 1,
}
}
opts.Debug = testutil.VerboseTest()
server, err := Mount(mntDir, root, opts)
if err != nil {
t.Fatal(err)
}
return mntDir, func() {
server.Unmount()
os.Remove(mntDir)
}
}
func TestDataFile(t *testing.T) {
want := "hello"
root := &Inode{}
mntDir, clean := testMount(t, root, &Options{
FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode()
ch := n.NewPersistentInode(
ctx,
&MemRegularFile{
Data: []byte(want),
Attr: fuse.Attr{
Mode: 0464,
},
},
NodeAttr{})
n.AddChild("file", ch, false)
},
})
defer clean()
var st syscall.Stat_t
if err := syscall.Lstat(mntDir+"/file", &st); err != nil {
t.Fatalf("Lstat: %v", err)
}
if want := uint32(syscall.S_IFREG | 0464); st.Mode != want {
t.Errorf("got mode %o, want %o", st.Mode, want)
}
fd, err := syscall.Open(mntDir+"/file", syscall.O_RDONLY, 0)
if err != nil {
t.Fatalf("Open: %v", err)
}
var buf [1024]byte
n, err := syscall.Read(fd, buf[:])
if err != nil {
t.Errorf("Read: %v", err)
}
if err := syscall.Close(fd); err != nil {
t.Errorf("Close: %v", err)
}
got := string(buf[:n])
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
func TestDataFileLargeRead(t *testing.T) {
root := &Inode{}
data := make([]byte, 256*1024)
rand.Read(data[:])
mntDir, clean := testMount(t, root, &Options{
FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode()
ch := n.NewPersistentInode(
ctx,
&MemRegularFile{
Data: data,
Attr: fuse.Attr{
Mode: 0464,
},
},
NodeAttr{})
n.AddChild("file", ch, false)
},
})
defer clean()
got, err := ioutil.ReadFile(mntDir + "/file")
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if !bytes.Equal(got, data) {
t.Errorf("roundtrip read had change")
}
}
type SymlinkerRoot struct {
Inode
}
func (s *SymlinkerRoot) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
l := &MemSymlink{
Data: []byte(target),
}
ch := s.NewPersistentInode(ctx, l, NodeAttr{Mode: syscall.S_IFLNK})
return ch, 0
}
func TestDataSymlink(t *testing.T) {
root := &SymlinkerRoot{}
mntDir, clean := testMount(t, root, nil)
defer clean()
if err := syscall.Symlink("target", mntDir+"/link"); err != nil {
t.Fatalf("Symlink: %v", err)
}
if got, err := os.Readlink(mntDir + "/link"); err != nil {
t.Fatalf("Readlink: %v", err)
} else if want := "target"; got != want {
t.Errorf("Readlink: got %q want %q", got, want)
}
}
......@@ -14,7 +14,7 @@ import (
// requests. This is a convenience wrapper around NewNodeFS and
// fuse.NewServer. If nil is given as options, default settings are
// applied, which are 1 second entry and attribute timeout.
func Mount(dir string, root DirOperations, options *Options) (*fuse.Server, error) {
func Mount(dir string, root InodeEmbedder, options *Options) (*fuse.Server, error) {
if options == nil {
oneSec := time.Second
options = &Options{
......
// 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 nodefs
import (
"context"
"path/filepath"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
)
func TestReadonlyCreate(t *testing.T) {
root := &Inode{}
mntDir, clean := testMount(t, root, nil)
defer clean()
_, err := syscall.Creat(mntDir+"/test", 0644)
if want := syscall.EROFS; want != err {
t.Fatalf("got err %v, want %v", err, want)
}
}
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{}, NodeAttr{Mode: syscall.S_IFDIR})
file := root.NewPersistentInode(ctx, &Inode{}, NodeAttr{Mode: syscall.S_IFREG})
root.AddChild("dir", dir, false)
root.AddChild("file", file, false)
},
})
defer clean()
for k, v := range map[string]uint32{
"dir": fuse.S_IFDIR | 0755,
"file": fuse.S_IFREG | 0644,
} {
var st syscall.Stat_t
if err := syscall.Lstat(filepath.Join(mntDir, k), &st); err != nil {
t.Error("Lstat", err)
} else if st.Mode != v {
t.Errorf("got %o want %o", st.Mode, v)
}
}
}
......@@ -28,7 +28,7 @@ type testCase struct {
origDir string
mntDir string
loopback DirOperations
loopback InodeEmbedder
rawFS fuse.RawFileSystem
server *fuse.Server
}
......@@ -437,7 +437,7 @@ func TestNotifyEntry(t *testing.T) {
t.Fatalf("got after %#v, want %#v", after, st)
}
if errno := tc.loopback.Inode().NotifyEntry("file"); errno != 0 {
if errno := tc.loopback.EmbeddedInode().NotifyEntry("file"); errno != 0 {
t.Errorf("notify failed: %v", errno)
}
......
......@@ -9,7 +9,7 @@ import (
"bytes"
"context"
"io/ioutil"
"os"
"log"
"path/filepath"
"reflect"
"strings"
......@@ -18,7 +18,6 @@ import (
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
)
var testData = map[string]string{
......@@ -62,20 +61,10 @@ func TestZipFS(t *testing.T) {
t.Fatal(err)
}
root := &zipRoot{r: r}
root := &zipRoot{zr: r}
mntDir, clean := testMount(t, root, nil)
defer clean()
mntDir := testutil.TempDir()
defer os.Remove(mntDir)
server, err := Mount(mntDir, root, &Options{
MountOptions: fuse.MountOptions{
Debug: testutil.VerboseTest(),
},
FirstAutomaticIno: 1,
})
if err != nil {
t.Fatal(err)
}
defer server.Unmount()
for k, v := range testData {
c, err := ioutil.ReadFile(filepath.Join(mntDir, k))
if err != nil {
......@@ -104,20 +93,48 @@ func TestZipFS(t *testing.T) {
}
}
func TestZipFSOnAdd(t *testing.T) {
zipBytes := createZip(testData)
r, err := zip.NewReader(&byteReaderAt{zipBytes}, int64(len(zipBytes)))
if err != nil {
t.Fatal(err)
}
zr := &zipRoot{zr: r}
root := &Inode{}
mnt, clean := testMount(t, root, &Options{
OnAdd: func(ctx context.Context) {
root.AddChild("sub",
root.NewPersistentInode(ctx, zr, NodeAttr{Mode: syscall.S_IFDIR}), false)
},
})
defer clean()
c, err := ioutil.ReadFile(mnt + "/sub/dir/subdir/subfile")
if err != nil {
t.Fatal("ReadFile", err)
}
if got, want := string(c), "content3"; got != want {
t.Errorf("got %q, want %q", got, want)
}
}
// zipFile is a file read from a zip archive.
type zipFile struct {
OperationStubs
Inode
file *zip.File
mu sync.Mutex
data []byte
}
var _ = (FileOperations)((*zipFile)(nil))
var _ = (Opener)((*zipFile)(nil))
var _ = (Getattrer)((*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, out *fuse.AttrOut) syscall.Errno {
func (zf *zipFile) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Size = zf.file.UncompressedSize64
return OK
}
......@@ -157,27 +174,29 @@ func (zf *zipFile) Read(ctx context.Context, f FileHandle, dest []byte, off int6
// zipRoot is the root of the Zip filesystem. Its only functionality
// is populating the filesystem.
type zipRoot struct {
OperationStubs
Inode
r *zip.Reader
zr *zip.Reader
}
var _ = (OnAdder)((*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.r.File {
for _, f := range zr.zr.File {
dir, base := filepath.Split(f.Name)
p := zr.Inode()
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, &OperationStubs{},
ch = p.NewPersistentInode(ctx, &Inode{},
NodeAttr{Mode: fuse.S_IFDIR})
p.AddChild(component, ch, true)
}
......@@ -188,3 +207,65 @@ func (zr *zipRoot) OnAdd(ctx context.Context) {
p.AddChild(base, ch, true)
}
}
// Persistent inodes can be used to create an in-memory
// prefabricated file system tree.
func ExampleInode_NewPersistentInode() {
// This is where we'll mount the FS
mntDir, _ := ioutil.TempDir("", "")
files := map[string]string{
"file": "content",
"subdir/other-file": "other-content",
}
root := &Inode{}
populate := func(ctx context.Context) {
for name, content := range files {
dir, base := filepath.Split(name)
p := root
// Add directories leading up to the file.
for _, component := range strings.Split(dir, "/") {
if len(component) == 0 {
continue
}
ch := p.GetChild(component)
if ch == nil {
// Create a directory
ch = p.NewPersistentInode(ctx, &Inode{},
NodeAttr{Mode: syscall.S_IFDIR})
// Add it
p.AddChild(component, ch, true)
}
p = ch
}
// Create the file
child := p.NewPersistentInode(ctx, &MemRegularFile{
Data: []byte(content),
}, NodeAttr{})
// And add it
p.AddChild(base, child, true)
}
}
server, err := Mount(mntDir, root, &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,
OnAdd: populate,
})
if err != nil {
log.Panic(err)
}
log.Printf("Mounted on %s", mntDir)
log.Printf("Unmount by calling 'fusermount -u %s'", mntDir)
server.Wait()
}
// Copyright 2016 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 zipfs
import (
"fmt"
"strings"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
type MemFile interface {
Stat(out *fuse.Attr)
Data() []byte
}
type memNode struct {
nodefs.Node
file MemFile
fs *MemTreeFs
}
// memTreeFs creates a tree of internal Inodes. Since the tree is
// loaded in memory completely at startup, it does not need inode
// discovery through Lookup() at serve time.
type MemTreeFs struct {
root *memNode
files map[string]MemFile
Name string
}
func NewMemTreeFs(files map[string]MemFile) *MemTreeFs {
fs := &MemTreeFs{
root: &memNode{Node: nodefs.NewDefaultNode()},
files: files,
}
fs.root.fs = fs
return fs
}
func (fs *MemTreeFs) String() string {
return fs.Name
}
func (fs *MemTreeFs) Root() nodefs.Node {
return fs.root
}
func (fs *MemTreeFs) onMount() {
for k, v := range fs.files {
fs.addFile(k, v)
}
fs.files = nil
}
func (n *memNode) OnMount(c *nodefs.FileSystemConnector) {
n.fs.onMount()
}
func (n *memNode) Print(indent int) {
s := ""
for i := 0; i < indent; i++ {
s = s + " "
}
children := n.Inode().Children()
for k, v := range children {
if v.IsDir() {
fmt.Println(s + k + ":")
mn, ok := v.Node().(*memNode)
if ok {
mn.Print(indent + 2)
}
} else {
fmt.Println(s + k)
}
}
}
func (n *memNode) OpenDir(context *fuse.Context) (stream []fuse.DirEntry, code fuse.Status) {
children := n.Inode().Children()
stream = make([]fuse.DirEntry, 0, len(children))
for k, v := range children {
mode := fuse.S_IFREG | 0666
if v.IsDir() {
mode = fuse.S_IFDIR | 0777
}
stream = append(stream, fuse.DirEntry{
Name: k,
Mode: uint32(mode),
})
}
return stream, fuse.OK
}
func (n *memNode) Open(flags uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {
if flags&fuse.O_ANYWRITE != 0 {
return nil, fuse.EPERM
}
return nodefs.NewDataFile(n.file.Data()), fuse.OK
}
func (n *memNode) Deletable() bool {
return false
}
func (n *memNode) GetAttr(out *fuse.Attr, file nodefs.File, context *fuse.Context) fuse.Status {
if n.Inode().IsDir() {
out.Mode = fuse.S_IFDIR | 0777
return fuse.OK
}
n.file.Stat(out)
out.Blocks = (out.Size + 511) / 512
return fuse.OK
}
func (n *MemTreeFs) addFile(name string, f MemFile) {
comps := strings.Split(name, "/")
node := n.root.Inode()
for i, c := range comps {
child := node.GetChild(c)
if child == nil {
fsnode := &memNode{
Node: nodefs.NewDefaultNode(),
fs: n,
}
if i == len(comps)-1 {
fsnode.file = f
}
child = node.NewChild(c, fsnode.file == nil, fsnode)
}
node = child
}
}
......@@ -15,191 +15,69 @@ symlinking path/to/zipfile to /config/zipmount
*/
import (
"context"
"log"
"path/filepath"
"sync"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/nodefs"
)
const (
CONFIG_PREFIX = "config/"
)
////////////////////////////////////////////////////////////////
// MultiZipFs is a path filesystem that mounts zipfiles.
// MultiZipFs is a filesystem that mounts zipfiles.
type MultiZipFs struct {
lock sync.RWMutex
zips map[string]nodefs.Node
dirZipFileMap map[string]string
// zip files that we are in the process of unmounting.
zombie map[string]bool
nodeFs *pathfs.PathNodeFs
pathfs.FileSystem
nodefs.Inode
}
func NewMultiZipFs() *MultiZipFs {
m := &MultiZipFs{
zips: make(map[string]nodefs.Node),
zombie: make(map[string]bool),
dirZipFileMap: make(map[string]string),
FileSystem: pathfs.NewDefaultFileSystem(),
}
return m
}
func (fs *MultiZipFs) OnAdd(ctx context.Context) {
n := fs.NewPersistentInode(ctx, &configRoot{}, nodefs.NodeAttr{Mode: syscall.S_IFDIR})
func (fs *MultiZipFs) String() string {
return "MultiZipFs"
fs.AddChild("config", n, false)
}
func (fs *MultiZipFs) OnMount(nodeFs *pathfs.PathNodeFs) {
fs.nodeFs = nodeFs
type configRoot struct {
nodefs.Inode
}
func (fs *MultiZipFs) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, code fuse.Status) {
fs.lock.RLock()
defer fs.lock.RUnlock()
var _ = (nodefs.Unlinker)((*configRoot)(nil))
var _ = (nodefs.Symlinker)((*configRoot)(nil))
stream = make([]fuse.DirEntry, 0, len(fs.zips)+2)
if name == "" {
var d fuse.DirEntry
d.Name = "config"
d.Mode = fuse.S_IFDIR | 0700
stream = append(stream, fuse.DirEntry(d))
func (r *configRoot) Unlink(ctx context.Context, basename string) syscall.Errno {
if r.GetChild(basename) == nil {
return syscall.ENOENT
}
if name == "config" {
for k := range fs.zips {
var d fuse.DirEntry
d.Name = k
d.Mode = fuse.S_IFLNK
stream = append(stream, fuse.DirEntry(d))
}
}
// XXX RmChild should return Inode?
return stream, fuse.OK
}
func (fs *MultiZipFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
a := &fuse.Attr{}
a.Owner = *fuse.CurrentOwner()
if name == "" {
// Should not write in top dir.
a.Mode = fuse.S_IFDIR | 0500
return a, fuse.OK
}
if name == "config" {
a.Mode = fuse.S_IFDIR | 0700
return a, fuse.OK
}
dir, base := filepath.Split(name)
if dir != "" && dir != CONFIG_PREFIX {
return nil, fuse.ENOENT
}
submode := uint32(fuse.S_IFDIR | 0700)
if dir == CONFIG_PREFIX {
submode = fuse.S_IFLNK | 0600
_, parent := r.Parent()
ch := parent.GetChild(basename)
if ch == nil {
return syscall.ENOENT
}
fs.lock.RLock()
defer fs.lock.RUnlock()
a.Mode = submode
_, hasDir := fs.zips[base]
if hasDir {
return a, fuse.OK
success, _ := parent.RmChild(basename)
if !success {
return syscall.EIO
}
return nil, fuse.ENOENT
ch.RmAllChildren()
parent.RmChild(basename)
parent.NotifyEntry(basename)
return 0
}
func (fs *MultiZipFs) Unlink(name string, context *fuse.Context) (code fuse.Status) {
dir, basename := filepath.Split(name)
if dir == CONFIG_PREFIX {
fs.lock.Lock()
defer fs.lock.Unlock()
if fs.zombie[basename] {
return fuse.ENOENT
}
root, ok := fs.zips[basename]
if !ok {
return fuse.ENOENT
}
name := fs.dirZipFileMap[basename]
fs.zombie[basename] = true
delete(fs.zips, basename)
delete(fs.dirZipFileMap, basename)
// Drop the lock to ensure that notify doesn't cause a deadlock.
fs.lock.Unlock()
code = fs.nodeFs.UnmountNode(root.Inode())
fs.lock.Lock()
delete(fs.zombie, basename)
if !code.Ok() {
// Failed: reinstate
fs.zips[basename] = root
fs.dirZipFileMap[basename] = name
}
return code
}
return fuse.EPERM
}
func (fs *MultiZipFs) Readlink(path string, context *fuse.Context) (val string, code fuse.Status) {
dir, base := filepath.Split(path)
if dir != CONFIG_PREFIX {
return "", fuse.ENOENT
}
fs.lock.Lock()
defer fs.lock.Unlock()
if fs.zombie[base] {
return "", fuse.ENOENT
}
zipfile, ok := fs.dirZipFileMap[base]
if !ok {
return "", fuse.ENOENT
}
return zipfile, fuse.OK
}
func (fs *MultiZipFs) Symlink(value string, linkName string, context *fuse.Context) (code fuse.Status) {
dir, base := filepath.Split(linkName)
if dir != CONFIG_PREFIX {
return fuse.EPERM
}
fs.lock.Lock()
defer fs.lock.Unlock()
if fs.zombie[base] {
return fuse.EBUSY
}
_, ok := fs.dirZipFileMap[base]
if ok {
return fuse.EBUSY
}
root, err := NewArchiveFileSystem(value)
func (r *configRoot) Symlink(ctx context.Context, target string, base string, out *fuse.EntryOut) (*nodefs.Inode, syscall.Errno) {
root, err := NewArchiveFileSystem(target)
if err != nil {
log.Println("NewZipArchiveFileSystem failed.", err)
return fuse.EINVAL
return nil, syscall.EINVAL
}
code = fs.nodeFs.Mount(base, root, nil)
if !code.Ok() {
return code
}
_, parent := r.Parent()
ch := r.NewPersistentInode(ctx, root, nodefs.NodeAttr{Mode: syscall.S_IFDIR})
parent.AddChild(base, ch, false)
fs.dirZipFileMap[base] = value
fs.zips[base] = root
return fuse.OK
link := r.NewPersistentInode(ctx, &nodefs.MemSymlink{
Data: []byte(target),
}, nodefs.NodeAttr{Mode: syscall.S_IFLNK})
r.AddChild(base, link, false)
return link, 0
}
......@@ -11,31 +11,29 @@ import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/nodefs"
)
const testTtl = 100 * time.Millisecond
func setupMzfs(t *testing.T) (mountPoint string, state *fuse.Server, cleanup func()) {
fs := NewMultiZipFs()
root := &MultiZipFs{}
mountPoint = testutil.TempDir()
nfs := pathfs.NewPathNodeFs(fs, nil)
state, _, err := nodefs.MountRoot(mountPoint, nfs.Root(), &nodefs.Options{
EntryTimeout: testTtl,
AttrTimeout: testTtl,
NegativeTimeout: 0.0,
Debug: testutil.VerboseTest(),
})
dt := testTtl
opts := &nodefs.Options{
EntryTimeout: &dt,
AttrTimeout: &dt,
}
opts.Debug = testutil.VerboseTest()
server, err := nodefs.Mount(mountPoint, root, opts)
if err != nil {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
state.WaitMount()
return mountPoint, state, func() {
state.Unmount()
return mountPoint, server, func() {
server.Unmount()
os.RemoveAll(mountPoint)
}
}
......
......@@ -9,11 +9,16 @@ import (
"bytes"
"compress/bzip2"
"compress/gzip"
"github.com/hanwen/go-fuse/fuse"
"context"
"io"
"log"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/nodefs"
)
// TODO - handle symlinks.
......@@ -26,23 +31,14 @@ func HeaderToFileInfo(out *fuse.Attr, h *tar.Header) {
out.SetTimes(&h.AccessTime, &h.ModTime, &h.ChangeTime)
}
type TarFile struct {
data []byte
tar.Header
}
func (f *TarFile) Stat(out *fuse.Attr) {
HeaderToFileInfo(out, &f.Header)
out.Mode |= syscall.S_IFREG
}
func (f *TarFile) Data() []byte {
return f.data
type tarRoot struct {
nodefs.Inode
rc io.ReadCloser
}
func NewTarTree(r io.Reader) map[string]MemFile {
files := map[string]MemFile{}
tr := tar.NewReader(r)
func (r *tarRoot) OnAdd(ctx context.Context) {
tr := tar.NewReader(r.rc)
defer r.rc.Close()
var longName *string
for {
......@@ -52,7 +48,9 @@ func NewTarTree(r io.Reader) map[string]MemFile {
break
}
if err != nil {
// handle error
log.Printf("Add: %v", err)
// XXX handle error
break
}
if hdr.Typeflag == 'L' {
......@@ -74,35 +72,70 @@ func NewTarTree(r io.Reader) map[string]MemFile {
buf := bytes.NewBuffer(make([]byte, 0, hdr.Size))
io.Copy(buf, tr)
dir, base := filepath.Split(filepath.Clean(hdr.Name))
p := r.EmbeddedInode()
for _, comp := range strings.Split(dir, "/") {
if len(comp) == 0 {
continue
}
ch := p.GetChild(comp)
if ch == nil {
ch = p.NewPersistentInode(ctx,
&nodefs.Inode{},
nodefs.NodeAttr{Mode: syscall.S_IFDIR})
p.AddChild(comp, ch, false)
}
p = ch
}
files[hdr.Name] = &TarFile{
Header: *hdr,
data: buf.Bytes(),
if hdr.Typeflag == tar.TypeSymlink {
p.AddChild(base, r.NewPersistentInode(ctx, &nodefs.MemSymlink{
Data: []byte(hdr.Linkname),
}, nodefs.NodeAttr{Mode: syscall.S_IFLNK}), false)
} else {
df := &nodefs.MemRegularFile{
Data: buf.Bytes(),
}
HeaderToFileInfo(&df.Attr, hdr)
p.AddChild(base, r.NewPersistentInode(ctx, df, nodefs.NodeAttr{}), false)
}
}
return files
}
func NewTarCompressedTree(name string, format string) (map[string]MemFile, error) {
type readCloser struct {
io.Reader
close func() error
}
func (rc *readCloser) Close() error {
return rc.close()
}
func NewTarCompressedTree(name string, format string) (nodefs.InodeEmbedder, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
var stream io.Reader
var stream io.ReadCloser
switch format {
case "gz":
unzip, err := gzip.NewReader(f)
if err != nil {
return nil, err
}
defer unzip.Close()
stream = unzip
stream = &readCloser{
unzip,
f.Close,
}
case "bz2":
unzip := bzip2.NewReader(f)
stream = unzip
stream = &readCloser{
unzip,
f.Close,
}
}
return NewTarTree(stream), nil
return &tarRoot{rc: stream}, nil
}
// Copyright 2016 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 zipfs
import (
"archive/tar"
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
"time"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/nodefs"
)
var tarContents = map[string]string{
"file.txt": "content",
"dir/subfile.txt": "other content",
}
type addClose struct {
io.Reader
}
func (c *addClose) Close() error {
return nil
}
func TestTar(t *testing.T) {
buf := &bytes.Buffer{}
w := tar.NewWriter(buf)
now := time.Now()
for k, v := range tarContents {
h := &tar.Header{
Name: k,
Size: int64(len(v)),
Mode: 0464,
Uid: 42,
Gid: 42,
ModTime: now,
}
isLink := filepath.Base(k) == "link"
if isLink {
h.Typeflag = tar.TypeSymlink
h.Linkname = v
}
w.WriteHeader(h)
if !isLink {
w.Write([]byte(v))
}
}
w.Close()
root := &tarRoot{rc: &addClose{buf}}
mnt := testutil.TempDir()
defer os.Remove(mnt)
opts := &nodefs.Options{}
opts.Debug = true
s, err := nodefs.Mount(mnt, root, opts)
if err != nil {
t.Errorf("Mount: %v", err)
}
defer s.Unmount()
for k, want := range tarContents {
p := filepath.Join(mnt, k)
var st syscall.Stat_t
if err := syscall.Lstat(p, &st); err != nil {
t.Fatalf("Stat %q: %v", p, err)
}
if filepath.Base(k) == "link" {
got, err := os.Readlink(p)
if err != nil {
t.Fatalf("Readlink: %v", err)
}
if got != want {
t.Errorf("Readlink: got %q want %q", got, want)
}
} else {
if got, want := st.Mode, uint32(syscall.S_IFREG|0464); got != want {
t.Errorf("got mode %o, want %o", got, want)
}
c, err := ioutil.ReadFile(p)
if err != nil {
t.Errorf("read %q: %v", k, err)
got := string(c)
if got != want {
t.Errorf("file %q: got %q, want %q", k, got, want)
}
}
}
}
}
......@@ -7,14 +7,18 @@ package zipfs
import (
"archive/zip"
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/nodefs"
)
type ZipFile struct {
......@@ -22,11 +26,6 @@ type ZipFile struct {
}
func (f *ZipFile) Stat(out *fuse.Attr) {
out.Mode = fuse.S_IFREG | uint32(f.File.Mode())
out.Size = uint64(f.File.UncompressedSize)
out.Mtime = uint64(f.File.ModTime().Unix())
out.Atime = out.Mtime
out.Ctime = out.Mtime
}
func (f *ZipFile) Data() []byte {
......@@ -44,41 +43,123 @@ func (f *ZipFile) Data() []byte {
return dest.Bytes()
}
type zipRoot struct {
nodefs.Inode
zr *zip.ReadCloser
}
var _ = (nodefs.OnAdder)((*zipRoot)(nil))
func (zr *zipRoot) OnAdd(ctx context.Context) {
for _, f := range zr.zr.File {
if f.FileInfo().IsDir() {
continue
}
dir, base := filepath.Split(filepath.Clean(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, &nodefs.Inode{},
nodefs.NodeAttr{Mode: fuse.S_IFDIR})
p.AddChild(component, ch, true)
}
p = ch
}
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, nodefs.NodeAttr{})
p.AddChild(base, ch, true)
}
}
// NewZipTree creates a new file-system for the zip file named name.
func NewZipTree(name string) (map[string]MemFile, error) {
func NewZipTree(name string) (nodefs.InodeEmbedder, error) {
r, err := zip.OpenReader(name)
if err != nil {
return nil, err
}
out := map[string]MemFile{}
for _, f := range r.File {
if strings.HasSuffix(f.Name, "/") {
continue
return &zipRoot{zr: r}, nil
}
// zipFile is a file read from a zip archive.
type zipFile struct {
nodefs.Inode
file *zip.File
mu sync.Mutex
data []byte
}
var _ = (nodefs.Opener)((*zipFile)(nil))
var _ = (nodefs.Getattrer)((*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 nodefs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = uint32(zf.file.Mode()) & 07777
out.Nlink = 1
out.Mtime = uint64(zf.file.ModTime().Unix())
out.Atime = out.Mtime
out.Ctime = out.Mtime
out.Size = zf.file.UncompressedSize64
out.Blocks = (out.Size + 511) / 512
return 0
}
// Open lazily unpacks zip data
func (zf *zipFile) Open(ctx context.Context, flags uint32) (nodefs.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
}
n := filepath.Clean(f.Name)
zf := &ZipFile{f}
out[n] = zf
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, 0
}
// Read simply returns the data that was already unpacked in the Open call
func (zf *zipFile) Read(ctx context.Context, f nodefs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := int(off) + len(dest)
if end > len(zf.data) {
end = len(zf.data)
}
return out, nil
return fuse.ReadResultData(zf.data[off:end]), 0
}
func NewArchiveFileSystem(name string) (root nodefs.Node, err error) {
var files map[string]MemFile
var _ = (nodefs.OnAdder)((*zipRoot)(nil))
func NewArchiveFileSystem(name string) (root nodefs.InodeEmbedder, err error) {
switch {
case strings.HasSuffix(name, ".zip"):
files, err = NewZipTree(name)
root, err = NewZipTree(name)
case strings.HasSuffix(name, ".tar.gz"):
files, err = NewTarCompressedTree(name, "gz")
root, err = NewTarCompressedTree(name, "gz")
case strings.HasSuffix(name, ".tar.bz2"):
files, err = NewTarCompressedTree(name, "bz2")
root, err = NewTarCompressedTree(name, "bz2")
case strings.HasSuffix(name, ".tar"):
f, err := os.Open(name)
if err != nil {
return nil, err
}
files = NewTarTree(f)
root = &tarRoot{rc: f}
default:
return nil, fmt.Errorf("unknown archive format %q", name)
}
......@@ -87,7 +168,5 @@ func NewArchiveFileSystem(name string) (root nodefs.Node, err error) {
return nil, err
}
mfs := NewMemTreeFs(files)
mfs.Name = fmt.Sprintf("fs(%s)", name)
return mfs.Root(), nil
return root, nil
}
......@@ -14,8 +14,8 @@ import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/nodefs"
)
func testZipFile() string {
......@@ -34,15 +34,12 @@ func setupZipfs(t *testing.T) (mountPoint string, cleanup func()) {
}
mountPoint = testutil.TempDir()
state, _, err := nodefs.MountRoot(mountPoint, root, &nodefs.Options{
Debug: testutil.VerboseTest(),
})
go state.Serve()
state.WaitMount()
opts := &nodefs.Options{}
opts.Debug = testutil.VerboseTest()
server, err := nodefs.Mount(mountPoint, root, opts)
return mountPoint, func() {
state.Unmount()
server.Unmount()
os.RemoveAll(mountPoint)
}
}
......@@ -70,19 +67,17 @@ func TestZipFs(t *testing.T) {
if err != nil {
t.Fatalf("Stat failed: %v", err)
}
if fi.Mode() != 0664 {
t.Fatalf("File mode 0%o != 0664", fi.Mode())
if got, want := fi.Mode(), 0664; int(got) != want {
t.Fatalf("File mode: got 0%o want 0%o", got, want)
}
if st := fi.Sys().(*syscall.Stat_t); st.Blocks != 1 {
t.Errorf("got block count %d, want 1", st.Blocks)
}
mtime, err := time.Parse(time.RFC3339, "2011-02-22T12:56:12Z")
if err != nil {
if want, err := time.Parse(time.RFC3339, "2011-02-22T12:56:12Z"); err != nil {
panic(err)
}
if !fi.ModTime().Equal(mtime) {
t.Fatalf("File mtime %v != %v", fi.ModTime(), mtime)
} else if !fi.ModTime().Equal(want) {
t.Fatalf("File mtime got %v, want %v", fi.ModTime(), want)
}
if fi.IsDir() {
......
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