From 8318020387fec43dc332f25dbdf98e2c6df85fa7 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood <nick@craig-wood.com>
Date: Mon, 21 Nov 2016 20:52:32 +0000
Subject: [PATCH] Implement mount2 with go-fuse

This passes the tests and works efficiently with the non sequential vfs ReadAt fix.
---
 cmd/all/all.go                  |   1 +
 cmd/mount2/file.go              | 154 ++++++++++++
 cmd/mount2/fs.go                | 131 +++++++++++
 cmd/mount2/mount.go             | 277 ++++++++++++++++++++++
 cmd/mount2/mount_test.go        |  13 ++
 cmd/mount2/mount_unsupported.go |   7 +
 cmd/mount2/node.go              | 400 ++++++++++++++++++++++++++++++++
 7 files changed, 983 insertions(+)
 create mode 100644 cmd/mount2/file.go
 create mode 100644 cmd/mount2/fs.go
 create mode 100644 cmd/mount2/mount.go
 create mode 100644 cmd/mount2/mount_test.go
 create mode 100644 cmd/mount2/mount_unsupported.go
 create mode 100644 cmd/mount2/node.go

diff --git a/cmd/all/all.go b/cmd/all/all.go
index 00108cdfe..894e04b1c 100644
--- a/cmd/all/all.go
+++ b/cmd/all/all.go
@@ -36,6 +36,7 @@ import (
 	_ "github.com/rclone/rclone/cmd/memtest"
 	_ "github.com/rclone/rclone/cmd/mkdir"
 	_ "github.com/rclone/rclone/cmd/mount"
+	_ "github.com/rclone/rclone/cmd/mount2"
 	_ "github.com/rclone/rclone/cmd/move"
 	_ "github.com/rclone/rclone/cmd/moveto"
 	_ "github.com/rclone/rclone/cmd/ncdu"
diff --git a/cmd/mount2/file.go b/cmd/mount2/file.go
new file mode 100644
index 000000000..d460c7b73
--- /dev/null
+++ b/cmd/mount2/file.go
@@ -0,0 +1,154 @@
+// +build linux darwin,amd64
+
+package mount2
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"os"
+	"syscall"
+
+	fusefs "github.com/hanwen/go-fuse/v2/fs"
+	"github.com/hanwen/go-fuse/v2/fuse"
+	"github.com/rclone/rclone/fs/log"
+	"github.com/rclone/rclone/vfs"
+)
+
+// FileHandle is a resource identifier for opened files. Usually, a
+// FileHandle should implement some of the FileXxxx interfaces.
+//
+// All of the FileXxxx operations can also be implemented at the
+// InodeEmbedder level, for example, one can implement NodeReader
+// instead of FileReader.
+//
+// FileHandles are useful in two cases: First, if the underlying
+// storage systems needs a handle for reading/writing. This is the
+// case with Unix system calls, which need a file descriptor (See also
+// the function `NewLoopbackFile`). Second, it is useful for
+// implementing files whose contents are not tied to an inode. For
+// example, a file like `/proc/interrupts` has no fixed content, but
+// changes on each open call. This means that each file handle must
+// have its own view of the content; this view can be tied to a
+// 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.
+type FileHandle struct {
+	h vfs.Handle
+}
+
+// Create a new FileHandle
+func newFileHandle(h vfs.Handle) *FileHandle {
+	return &FileHandle{
+		h: h,
+	}
+}
+
+// Check interface satistfied
+var _ fusefs.FileHandle = (*FileHandle)(nil)
+
+// The String method is for debug printing.
+func (f *FileHandle) String() string {
+	return fmt.Sprintf("fh=%p(%s)", f, f.h.Node().Path())
+}
+
+// Read data from a file. The data should be returned as
+// ReadResult, which may be constructed from the incoming
+// `dest` buffer.
+func (f *FileHandle) Read(ctx context.Context, dest []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
+	var n int
+	var err error
+	defer log.Trace(f, "off=%d", off)("n=%d, off=%d, errno=%v", &n, &off, &errno)
+	n, err = f.h.ReadAt(dest, off)
+	if err == io.EOF {
+		err = nil
+	}
+	return fuse.ReadResultData(dest[:n]), translateError(err)
+}
+
+var _ fusefs.FileReader = (*FileHandle)(nil)
+
+// Write the data into the file handle at given offset. After
+// returning, the data will be reused and may not referenced.
+func (f *FileHandle) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) {
+	var n int
+	var err error
+	defer log.Trace(f, "off=%d", off)("n=%d, off=%d, errno=%v", &n, &off, &errno)
+	if f.h.Node().VFS().Opt.CacheMode < vfs.CacheModeWrites || f.h.Node().Mode()&os.ModeAppend == 0 {
+		n, err = f.h.WriteAt(data, off)
+	} else {
+		n, err = f.h.Write(data)
+	}
+	return uint32(n), translateError(err)
+}
+
+var _ fusefs.FileWriter = (*FileHandle)(nil)
+
+// Flush is called for the close(2) call on a file descriptor. In case
+// of a descriptor that was duplicated using dup(2), it may be called
+// more than once for the same FileHandle.
+func (f *FileHandle) Flush(ctx context.Context) syscall.Errno {
+	return translateError(f.h.Flush())
+}
+
+var _ fusefs.FileFlusher = (*FileHandle)(nil)
+
+// Release is called to before a FileHandle 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.
+func (f *FileHandle) Release(ctx context.Context) syscall.Errno {
+	return translateError(f.h.Release())
+}
+
+var _ fusefs.FileReleaser = (*FileHandle)(nil)
+
+// Fsync is a signal to ensure writes to the Inode are flushed
+// to stable storage.
+func (f *FileHandle) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
+	return translateError(f.h.Sync())
+}
+
+var _ fusefs.FileFsyncer = (*FileHandle)(nil)
+
+// Getattr reads attributes for an Inode. The library will ensure that
+// Mode and Ino are set correctly. For files that are not opened with
+// FOPEN_DIRECTIO, Size should be set so it can be read correctly.  If
+// returning zeroed permissions, the default behavior is to change the
+// mode of 0755 (directory) or 0644 (files). This can be switched off
+// with the Options.NullPermissions setting. If blksize is unset, 4096
+// is assumed, and the 'blocks' field is set accordingly.
+func (f *FileHandle) Getattr(ctx context.Context, out *fuse.AttrOut) (errno syscall.Errno) {
+	defer log.Trace(f, "")("attr=%v, errno=%v", &out, &errno)
+	setAttrOut(f.h.Node(), out)
+	return 0
+}
+
+var _ fusefs.FileGetattrer = (*FileHandle)(nil)
+
+// Setattr sets attributes for an Inode.
+func (f *FileHandle) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
+	defer log.Trace(f, "in=%v", in)("attr=%v, errno=%v", &out, &errno)
+	var err error
+	setAttrOut(f.h.Node(), out)
+	size, ok := in.GetSize()
+	if ok {
+		err = f.h.Truncate(int64(size))
+		if err != nil {
+			return translateError(err)
+		}
+		out.Attr.Size = size
+	}
+	mtime, ok := in.GetMTime()
+	if ok {
+		err = f.h.Node().SetModTime(mtime)
+		if err != nil {
+			return translateError(err)
+		}
+		out.Attr.Mtime = uint64(mtime.Unix())
+		out.Attr.Mtimensec = uint32(mtime.Nanosecond())
+	}
+	return 0
+}
+
+var _ fusefs.FileSetattrer = (*FileHandle)(nil)
diff --git a/cmd/mount2/fs.go b/cmd/mount2/fs.go
new file mode 100644
index 000000000..f672b48b5
--- /dev/null
+++ b/cmd/mount2/fs.go
@@ -0,0 +1,131 @@
+// FUSE main Fs
+
+// +build linux darwin,amd64
+
+package mount2
+
+import (
+	"os"
+	"syscall"
+
+	"github.com/hanwen/go-fuse/v2/fuse"
+	"github.com/pkg/errors"
+	"github.com/rclone/rclone/cmd/mountlib"
+	"github.com/rclone/rclone/fs"
+	"github.com/rclone/rclone/fs/log"
+	"github.com/rclone/rclone/vfs"
+	"github.com/rclone/rclone/vfs/vfsflags"
+)
+
+// FS represents the top level filing system
+type FS struct {
+	VFS *vfs.VFS
+	f   fs.Fs
+}
+
+// NewFS creates a pathfs.FileSystem from the fs.Fs passed in
+func NewFS(f fs.Fs) *FS {
+	fsys := &FS{
+		VFS: vfs.New(f, &vfsflags.Opt),
+		f:   f,
+	}
+	return fsys
+}
+
+// Root returns the root node
+func (f *FS) Root() (node *Node, err error) {
+	defer log.Trace("", "")("node=%+v, err=%v", &node, &err)
+	root, err := f.VFS.Root()
+	if err != nil {
+		return nil, err
+	}
+	return newNode(f, root), nil
+}
+
+// SetDebug if called, provide debug output through the log package.
+func (f *FS) SetDebug(debug bool) {
+	fs.Debugf(f.f, "SetDebug %v", debug)
+}
+
+// get the Mode from a vfs Node
+func getMode(node os.FileInfo) uint32 {
+	Mode := node.Mode().Perm()
+	if node.IsDir() {
+		Mode |= fuse.S_IFDIR
+	} else {
+		Mode |= fuse.S_IFREG
+	}
+	return uint32(Mode)
+}
+
+// fill in attr from node
+func setAttr(node vfs.Node, attr *fuse.Attr) {
+	Size := uint64(node.Size())
+	const BlockSize = 512
+	Blocks := (Size + BlockSize - 1) / BlockSize
+	modTime := node.ModTime()
+	// set attributes
+	vfs := node.VFS()
+	attr.Owner.Gid = vfs.Opt.UID
+	attr.Owner.Uid = vfs.Opt.GID
+	attr.Mode = getMode(node)
+	attr.Size = Size
+	attr.Nlink = 1
+	attr.Blocks = Blocks
+	// attr.Blksize = BlockSize // not supported in freebsd/darwin, defaults to 4k if not set
+	s := uint64(modTime.Unix())
+	ns := uint32(modTime.Nanosecond())
+	attr.Atime = s
+	attr.Atimensec = ns
+	attr.Mtime = s
+	attr.Mtimensec = ns
+	attr.Ctime = s
+	attr.Ctimensec = ns
+	//attr.Rdev
+}
+
+// fill in AttrOut from node
+func setAttrOut(node vfs.Node, out *fuse.AttrOut) {
+	setAttr(node, &out.Attr)
+	out.SetTimeout(mountlib.AttrTimeout)
+}
+
+// fill in EntryOut from node
+func setEntryOut(node vfs.Node, out *fuse.EntryOut) {
+	setAttr(node, &out.Attr)
+	out.SetEntryTimeout(mountlib.AttrTimeout)
+	out.SetAttrTimeout(mountlib.AttrTimeout)
+}
+
+// Translate errors from mountlib into Syscall error numbers
+func translateError(err error) syscall.Errno {
+	if err == nil {
+		return 0
+	}
+	switch errors.Cause(err) {
+	case vfs.OK:
+		return 0
+	case vfs.ENOENT:
+		return syscall.ENOENT
+	case vfs.EEXIST:
+		return syscall.EEXIST
+	case vfs.EPERM:
+		return syscall.EPERM
+	case vfs.ECLOSED:
+		return syscall.EBADF
+	case vfs.ENOTEMPTY:
+		return syscall.ENOTEMPTY
+	case vfs.ESPIPE:
+		return syscall.ESPIPE
+	case vfs.EBADF:
+		return syscall.EBADF
+	case vfs.EROFS:
+		return syscall.EROFS
+	case vfs.ENOSYS:
+		return syscall.ENOSYS
+	case vfs.EINVAL:
+		return syscall.EINVAL
+	}
+	fs.Errorf(nil, "IO error: %v", err)
+	return syscall.EIO
+}
diff --git a/cmd/mount2/mount.go b/cmd/mount2/mount.go
new file mode 100644
index 000000000..fc6362639
--- /dev/null
+++ b/cmd/mount2/mount.go
@@ -0,0 +1,277 @@
+// Package mount implents a FUSE mounting system for rclone remotes.
+
+// +build linux darwin,amd64
+
+package mount2
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"os/signal"
+	"runtime"
+	"syscall"
+
+	fusefs "github.com/hanwen/go-fuse/v2/fs"
+	"github.com/hanwen/go-fuse/v2/fuse"
+	"github.com/okzk/sdnotify"
+	"github.com/pkg/errors"
+	"github.com/rclone/rclone/cmd/mountlib"
+	"github.com/rclone/rclone/fs"
+	"github.com/rclone/rclone/lib/atexit"
+	"github.com/rclone/rclone/vfs"
+)
+
+func init() {
+	mountlib.NewMountCommand("mount2", Mount)
+}
+
+// mountOptions configures the options from the command line flags
+//
+// man mount.fuse for more info and note the -o flag for other options
+func mountOptions(fsys *FS, f fs.Fs) (mountOpts *fuse.MountOptions) {
+	device := f.Name() + ":" + f.Root()
+	mountOpts = &fuse.MountOptions{
+		AllowOther:    mountlib.AllowOther,
+		FsName:        device,
+		Name:          "rclone",
+		DisableXAttrs: true,
+		Debug:         mountlib.DebugFUSE,
+		MaxReadAhead:  int(mountlib.MaxReadAhead),
+
+		// RememberInodes: true,
+		// SingleThreaded: true,
+
+		/*
+			AllowOther bool
+
+			// Options are passed as -o string to fusermount.
+			Options []string
+
+			// Default is _DEFAULT_BACKGROUND_TASKS, 12.  This numbers
+			// controls the allowed number of requests that relate to
+			// async I/O.  Concurrency for synchronous I/O is not limited.
+			MaxBackground int
+
+			// Write size to use.  If 0, use default. This number is
+			// capped at the kernel maximum.
+			MaxWrite int
+
+			// Max read ahead to use.  If 0, use default. This number is
+			// capped at the kernel maximum.
+			MaxReadAhead int
+
+			// If IgnoreSecurityLabels is set, all security related xattr
+			// requests will return NO_DATA without passing through the
+			// user defined filesystem.  You should only set this if you
+			// file system implements extended attributes, and you are not
+			// interested in security labels.
+			IgnoreSecurityLabels bool // ignoring labels should be provided as a fusermount mount option.
+
+			// If RememberInodes is set, we will never forget inodes.
+			// This may be useful for NFS.
+			RememberInodes bool
+
+			// Values shown in "df -T" and friends
+			// First column, "Filesystem"
+			FsName string
+
+			// Second column, "Type", will be shown as "fuse." + Name
+			Name string
+
+			// If set, wrap the file system in a single-threaded locking wrapper.
+			SingleThreaded bool
+
+			// If set, return ENOSYS for Getxattr calls, so the kernel does not issue any
+			// Xattr operations at all.
+			DisableXAttrs bool
+
+			// If set, print debugging information.
+			Debug bool
+
+			// If set, ask kernel to forward file locks to FUSE. If using,
+			// you must implement the GetLk/SetLk/SetLkw methods.
+			EnableLocks bool
+
+			// If set, ask kernel not to do automatic data cache invalidation.
+			// The filesystem is fully responsible for invalidating data cache.
+			ExplicitDataCacheControl bool
+		*/
+
+	}
+	var opts []string
+	// FIXME doesn't work opts = append(opts, fmt.Sprintf("max_readahead=%d", maxReadAhead))
+	if mountlib.AllowNonEmpty {
+		opts = append(opts, "nonempty")
+	}
+	if mountlib.AllowOther {
+		opts = append(opts, "allow_other")
+	}
+	if mountlib.AllowRoot {
+		opts = append(opts, "allow_root")
+	}
+	if mountlib.DefaultPermissions {
+		opts = append(opts, "default_permissions")
+	}
+	if fsys.VFS.Opt.ReadOnly {
+		opts = append(opts, "ro")
+	}
+	if mountlib.WritebackCache {
+		log.Printf("FIXME --write-back-cache not supported")
+		// FIXME opts = append(opts,fuse.WritebackCache())
+	}
+	// Some OS X only options
+	if runtime.GOOS == "darwin" {
+		opts = append(opts,
+			// VolumeName sets the volume name shown in Finder.
+			fmt.Sprintf("volname=%s", device),
+
+			// NoAppleXattr makes OSXFUSE disallow extended attributes with the
+			// prefix "com.apple.". This disables persistent Finder state and
+			// other such information.
+			"noapplexattr",
+
+			// NoAppleDouble makes OSXFUSE disallow files with names used by OS X
+			// to store extended attributes on file systems that do not support
+			// them natively.
+			//
+			// Such file names are:
+			//
+			//     ._*
+			//     .DS_Store
+			"noappledouble",
+		)
+	}
+	mountOpts.Options = opts
+	return mountOpts
+}
+
+// mount the file system
+//
+// The mount point will be ready when this returns.
+//
+// returns an error, and an error channel for the serve process to
+// report an error when fusermount is called.
+func mount(f fs.Fs, mountpoint string) (*vfs.VFS, <-chan error, func() error, error) {
+	fs.Debugf(f, "Mounting on %q", mountpoint)
+
+	fsys := NewFS(f)
+	// nodeFsOpts := &fusefs.PathNodeFsOptions{
+	// 	ClientInodes: false,
+	// 	Debug:        mountlib.DebugFUSE,
+	// }
+	// nodeFs := fusefs.NewPathNodeFs(fsys, nodeFsOpts)
+
+	//mOpts := fusefs.NewOptions() // default options
+	// FIXME
+	// mOpts.EntryTimeout = 10 * time.Second
+	// mOpts.AttrTimeout = 10 * time.Second
+	// mOpts.NegativeTimeout = 10 * time.Second
+	//mOpts.Debug = mountlib.DebugFUSE
+
+	//conn := fusefs.NewFileSystemConnector(nodeFs.Root(), mOpts)
+	mountOpts := mountOptions(fsys, f)
+
+	// FIXME fill out
+	opts := fusefs.Options{
+		MountOptions: *mountOpts,
+		EntryTimeout: &mountlib.AttrTimeout,
+		AttrTimeout:  &mountlib.AttrTimeout,
+		// UID
+		// GID
+	}
+
+	root, err := fsys.Root()
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	rawFS := fusefs.NewNodeFS(root, &opts)
+	server, err := fuse.NewServer(rawFS, mountpoint, &opts.MountOptions)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	//mountOpts := &fuse.MountOptions{}
+	//server, err := fusefs.Mount(mountpoint, fsys, &opts)
+	// server, err := fusefs.Mount(mountpoint, root, &opts)
+	// if err != nil {
+	// 	return nil, nil, nil, err
+	// }
+
+	umount := func() error {
+		// Shutdown the VFS
+		fsys.VFS.Shutdown()
+		return server.Unmount()
+	}
+
+	// serverSettings := server.KernelSettings()
+	// fs.Debugf(f, "Server settings %+v", serverSettings)
+
+	// Serve the mount point in the background returning error to errChan
+	errs := make(chan error, 1)
+	go func() {
+		server.Serve()
+		errs <- nil
+	}()
+
+	fs.Debugf(f, "Waiting for the mount to start...")
+	err = server.WaitMount()
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	fs.Debugf(f, "Mount started")
+	return fsys.VFS, errs, umount, nil
+}
+
+// Mount mounts the remote at mountpoint.
+//
+// If noModTime is set then it
+func Mount(f fs.Fs, mountpoint string) error {
+	// Mount it
+	vfs, errChan, unmount, err := mount(f, mountpoint)
+	if err != nil {
+		return errors.Wrap(err, "failed to mount FUSE fs")
+	}
+
+	sigInt := make(chan os.Signal, 1)
+	signal.Notify(sigInt, syscall.SIGINT, syscall.SIGTERM)
+	sigHup := make(chan os.Signal, 1)
+	signal.Notify(sigHup, syscall.SIGHUP)
+	atexit.Register(func() {
+		_ = unmount()
+	})
+
+	if err := sdnotify.Ready(); err != nil && err != sdnotify.ErrSdNotifyNoSocket {
+		return errors.Wrap(err, "failed to notify systemd")
+	}
+
+waitloop:
+	for {
+		select {
+		// umount triggered outside the app
+		case err = <-errChan:
+			break waitloop
+		// Program abort: umount
+		case <-sigInt:
+			err = unmount()
+			break waitloop
+		// user sent SIGHUP to clear the cache
+		case <-sigHup:
+			root, err := vfs.Root()
+			if err != nil {
+				fs.Errorf(f, "Error reading root: %v", err)
+			} else {
+				root.ForgetAll()
+			}
+		}
+	}
+
+	_ = sdnotify.Stopping()
+	if err != nil {
+		return errors.Wrap(err, "failed to umount FUSE fs")
+	}
+
+	return nil
+}
diff --git a/cmd/mount2/mount_test.go b/cmd/mount2/mount_test.go
new file mode 100644
index 000000000..32bd83b7d
--- /dev/null
+++ b/cmd/mount2/mount_test.go
@@ -0,0 +1,13 @@
+// +build linux darwin,amd64
+
+package mount2
+
+import (
+	"testing"
+
+	"github.com/rclone/rclone/cmd/mountlib/mounttest"
+)
+
+func TestMount(t *testing.T) {
+	mounttest.RunTests(t, mount)
+}
diff --git a/cmd/mount2/mount_unsupported.go b/cmd/mount2/mount_unsupported.go
new file mode 100644
index 000000000..7adf1e72f
--- /dev/null
+++ b/cmd/mount2/mount_unsupported.go
@@ -0,0 +1,7 @@
+// Build for mount for unsupported platforms to stop go complaining
+// about "no buildable Go source files "
+
+// +build !linux
+// +build !darwin !amd64
+
+package mount2
diff --git a/cmd/mount2/node.go b/cmd/mount2/node.go
new file mode 100644
index 000000000..3f00cdcec
--- /dev/null
+++ b/cmd/mount2/node.go
@@ -0,0 +1,400 @@
+// +build linux darwin,amd64
+
+package mount2
+
+import (
+	"context"
+	"os"
+	"path"
+	"syscall"
+
+	fusefs "github.com/hanwen/go-fuse/v2/fs"
+	"github.com/hanwen/go-fuse/v2/fuse"
+	"github.com/rclone/rclone/cmd/mountlib"
+	"github.com/rclone/rclone/fs"
+	"github.com/rclone/rclone/fs/log"
+	"github.com/rclone/rclone/vfs"
+)
+
+// Node represents a directory or file
+type Node struct {
+	fusefs.Inode
+	node vfs.Node
+	fsys *FS
+}
+
+// Node types must be InodeEmbedders
+var _ fusefs.InodeEmbedder = (*Node)(nil)
+
+// newNode creates a new fusefs.Node from a vfs Node
+func newNode(fsys *FS, node vfs.Node) *Node {
+	return &Node{
+		node: node,
+		fsys: fsys,
+	}
+}
+
+// String used for pretty printing.
+func (n *Node) String() string {
+	return n.node.Path()
+}
+
+// lookup a Node in a directory
+func (n *Node) lookupVfsNodeInDir(leaf string) (vfsNode vfs.Node, errno syscall.Errno) {
+	dir, ok := n.node.(*vfs.Dir)
+	if !ok {
+		return nil, syscall.ENOTDIR
+	}
+	vfsNode, err := dir.Stat(leaf)
+	return vfsNode, translateError(err)
+}
+
+// // lookup a Dir given a path
+// func (n *Node) lookupDir(path string) (dir *vfs.Dir, code fuse.Status) {
+// 	node, code := fsys.lookupVfsNodeInDir(path)
+// 	if !code.Ok() {
+// 		return nil, code
+// 	}
+// 	dir, ok := n.(*vfs.Dir)
+// 	if !ok {
+// 		return nil, fuse.ENOTDIR
+// 	}
+// 	return dir, fuse.OK
+// }
+
+// // lookup a parent Dir given a path returning the dir and the leaf
+// func (n *Node) lookupParentDir(filePath string) (leaf string, dir *vfs.Dir, code fuse.Status) {
+// 	parentDir, leaf := path.Split(filePath)
+// 	dir, code = fsys.lookupDir(parentDir)
+// 	return leaf, dir, code
+// }
+
+// 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.
+func (n *Node) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
+	defer log.Trace(n, "")("out=%+v", &out)
+	out = new(fuse.StatfsOut)
+	const blockSize = 4096
+	const fsBlocks = (1 << 50) / blockSize
+	out.Blocks = fsBlocks  // Total data blocks in file system.
+	out.Bfree = fsBlocks   // Free blocks in file system.
+	out.Bavail = fsBlocks  // Free blocks in file system if you're not root.
+	out.Files = 1E9        // Total files in file system.
+	out.Ffree = 1E9        // Free files in file system.
+	out.Bsize = blockSize  // Block size
+	out.NameLen = 255      // Maximum file name length?
+	out.Frsize = blockSize // Fragment size, smallest addressable data size in the file system.
+	mountlib.ClipBlocks(&out.Blocks)
+	mountlib.ClipBlocks(&out.Bfree)
+	mountlib.ClipBlocks(&out.Bavail)
+	return 0
+}
+
+var _ = (fusefs.NodeStatfser)((*Node)(nil))
+
+// Getattr reads attributes for an Inode. The library will ensure that
+// Mode and Ino are set correctly. For files that are not opened with
+// FOPEN_DIRECTIO, Size should be set so it can be read correctly.  If
+// returning zeroed permissions, the default behavior is to change the
+// mode of 0755 (directory) or 0644 (files). This can be switched off
+// with the Options.NullPermissions setting. If blksize is unset, 4096
+// is assumed, and the 'blocks' field is set accordingly.
+func (n *Node) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno {
+	setAttrOut(n.node, out)
+	return 0
+}
+
+var _ = (fusefs.NodeGetattrer)((*Node)(nil))
+
+// Setattr sets attributes for an Inode.
+func (n *Node) Setattr(ctx context.Context, f fusefs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
+	defer log.Trace(n, "in=%v", in)("out=%#v, errno=%v", &out, &errno)
+	var err error
+	setAttrOut(n.node, out)
+	size, ok := in.GetSize()
+	if ok {
+		err = n.node.Truncate(int64(size))
+		if err != nil {
+			return translateError(err)
+		}
+		out.Attr.Size = size
+	}
+	mtime, ok := in.GetMTime()
+	if ok {
+		err = n.node.SetModTime(mtime)
+		if err != nil {
+			return translateError(err)
+		}
+		out.Attr.Mtime = uint64(mtime.Unix())
+		out.Attr.Mtimensec = uint32(mtime.Nanosecond())
+	}
+	return 0
+}
+
+var _ = (fusefs.NodeSetattrer)((*Node)(nil))
+
+// Open opens an Inode (of regular file type) for reading. It
+// is optional but recommended to return a FileHandle.
+func (n *Node) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+	defer log.Trace(n, "flags=%#o", flags)("errno=%v", &errno)
+	// fuse flags are based off syscall flags as are os flags, so
+	// should be compatible
+	handle, err := n.node.Open(int(flags))
+	if err != nil {
+		return nil, 0, translateError(err)
+	}
+	// If size unknown then use direct io to read
+	if entry := n.node.DirEntry(); entry != nil && entry.Size() < 0 {
+		fuseFlags |= fuse.FOPEN_DIRECT_IO
+	}
+	return newFileHandle(handle), fuseFlags, 0
+}
+
+var _ = (fusefs.NodeOpener)((*Node)(nil))
+
+// Lookup should find a direct child of a directory by the child's 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 a directory does not implement NodeLookuper, the library looks
+// for an existing child with the given name.
+//
+// The input to a Lookup is {parent directory, name string}.
+//
+// Lookup, if successful, must return an *Inode. Once the Inode is
+// returned to the kernel, the kernel can issue further operations,
+// such as Open or Getxattr on that node.
+//
+// A successful Lookup also returns an EntryOut. Among others, this
+// contains file attributes (mode, size, mtime, etc.).
+//
+// FUSE supports other operations that modify the namespace. For
+// example, the Symlink, Create, Mknod, Link methods all create new
+// children in directories. Hence, they also return *Inode and must
+// populate their fuse.EntryOut arguments.
+func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) {
+	defer log.Trace(n, "name=%q", name)("inode=%v, attr=%v, errno=%v", &inode, &out, &errno)
+	vfsNode, errno := n.lookupVfsNodeInDir(name)
+	if errno != 0 {
+		return nil, errno
+	}
+	newNode := &Node{
+		node: vfsNode,
+		fsys: n.fsys,
+	}
+
+	// FIXME
+	// out.SetEntryTimeout(dt time.Duration)
+	// out.SetAttrTimeout(dt time.Duration)
+	setEntryOut(vfsNode, out)
+
+	return n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode}), 0
+}
+
+var _ = (fusefs.NodeLookuper)((*Node)(nil))
+
+// 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.
+func (n *Node) Opendir(ctx context.Context) syscall.Errno {
+	if !n.node.IsDir() {
+		return syscall.ENOTDIR
+	}
+	return 0
+}
+
+var _ = (fusefs.NodeOpendirer)((*Node)(nil))
+
+type dirStream struct {
+	nodes []os.FileInfo
+	i     int
+}
+
+// HasNext indicates if there are further entries. HasNext
+// might be called on already closed streams.
+func (ds *dirStream) HasNext() bool {
+	return ds.i < len(ds.nodes)
+}
+
+// Next retrieves the next entry. It is only called if HasNext
+// has previously returned true.  The Errno return may be used to
+// indicate I/O errors
+func (ds *dirStream) Next() (de fuse.DirEntry, errno syscall.Errno) {
+	// defer log.Trace(nil, "")("de=%+v, errno=%v", &de, &errno)
+	fi := ds.nodes[ds.i]
+	de = fuse.DirEntry{
+		// Mode is the file's mode. Only the high bits (eg. S_IFDIR)
+		// are considered.
+		Mode: getMode(fi),
+
+		// Name is the basename of the file in the directory.
+		Name: path.Base(fi.Name()),
+
+		// Ino is the inode number.
+		Ino: 0, // FIXME
+	}
+	ds.i++
+	return de, 0
+}
+
+// Close releases resources related to this directory
+// stream.
+func (ds *dirStream) Close() {
+}
+
+var _ fusefs.DirStream = (*dirStream)(nil)
+
+// Readdir opens a stream of directory entries.
+//
+// Readdir essentiallly returns a list of strings, and it is allowed
+// for Readdir to return different results from Lookup. For example,
+// you can return nothing for Readdir ("ls my-fuse-mount" is empty),
+// while still implementing Lookup ("ls my-fuse-mount/a-specific-file"
+// shows a single file).
+//
+// If a directory does not implement NodeReaddirer, a list of
+// currently known children from the tree is returned. This means that
+// static in-memory file systems need not implement NodeReaddirer.
+func (n *Node) Readdir(ctx context.Context) (ds fusefs.DirStream, errno syscall.Errno) {
+	defer log.Trace(n, "")("ds=%v, errno=%v", &ds, &errno)
+	if !n.node.IsDir() {
+		return nil, syscall.ENOTDIR
+	}
+	fh, err := n.node.Open(os.O_RDONLY)
+	if err != nil {
+		return nil, translateError(err)
+	}
+	defer func() {
+		closeErr := fh.Close()
+		if errno == 0 && closeErr != nil {
+			errno = translateError(closeErr)
+		}
+	}()
+	items, err := fh.Readdir(-1)
+	if err != nil {
+		return nil, translateError(err)
+	}
+	return &dirStream{
+		nodes: items,
+	}, 0
+}
+
+var _ = (fusefs.NodeReaddirer)((*Node)(nil))
+
+// Mkdir is similar to Lookup, but must create a directory entry and Inode.
+// Default is to return EROFS.
+func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) {
+	defer log.Trace(name, "mode=0%o", mode)("inode=%v, errno=%v", &inode, &errno)
+	dir, ok := n.node.(*vfs.Dir)
+	if !ok {
+		return nil, syscall.ENOTDIR
+	}
+	newDir, err := dir.Mkdir(name)
+	if err != nil {
+		return nil, translateError(err)
+	}
+	newNode := newNode(n.fsys, newDir)
+	setEntryOut(newNode.node, out)
+	newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode})
+	return newInode, 0
+}
+
+var _ = (fusefs.NodeMkdirer)((*Node)(nil))
+
+// 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.
+func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fusefs.Inode, fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+	defer log.Trace(n, "name=%q, flags=%#o, mode=%#o", name, flags, mode)("node=%v, fh=%v, flags=%#o, errno=%v", &node, &fh, &fuseFlags, &errno)
+	dir, ok := n.node.(*vfs.Dir)
+	if !ok {
+		return nil, nil, 0, syscall.ENOTDIR
+	}
+	// translate the fuse flags to os flags
+	osFlags := int(flags) | os.O_CREATE
+	file, err := dir.Create(name, osFlags)
+	if err != nil {
+		return nil, nil, 0, translateError(err)
+	}
+	handle, err := file.Open(osFlags)
+	if err != nil {
+		return nil, nil, 0, translateError(err)
+	}
+	fh = newFileHandle(handle)
+	// FIXME
+	// fh = &fusefs.WithFlags{
+	// 	File: fh,
+	// 	//FuseFlags: fuse.FOPEN_NONSEEKABLE,
+	// 	OpenFlags: flags,
+	// }
+
+	// Find the created node
+	vfsNode, errno := n.lookupVfsNodeInDir(name)
+	if errno != 0 {
+		return nil, nil, 0, errno
+	}
+	setEntryOut(vfsNode, out)
+	newNode := newNode(n.fsys, vfsNode)
+	fs.Debugf(nil, "attr=%#v", out.Attr)
+	newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode})
+	return newInode, fh, 0, 0
+}
+
+var _ = (fusefs.NodeCreater)((*Node)(nil))
+
+// 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.
+func (n *Node) Unlink(ctx context.Context, name string) (errno syscall.Errno) {
+	defer log.Trace(n, "name=%q", name)("errno=%v", &errno)
+	vfsNode, errno := n.lookupVfsNodeInDir(name)
+	if errno != 0 {
+		return errno
+	}
+	return translateError(vfsNode.Remove())
+}
+
+var _ = (fusefs.NodeUnlinker)((*Node)(nil))
+
+// Rmdir is like Unlink but for directories.
+// Default is to return EROFS.
+func (n *Node) Rmdir(ctx context.Context, name string) (errno syscall.Errno) {
+	defer log.Trace(n, "name=%q", name)("errno=%v", &errno)
+	vfsNode, errno := n.lookupVfsNodeInDir(name)
+	if errno != 0 {
+		return errno
+	}
+	return translateError(vfsNode.Remove())
+}
+
+var _ = (fusefs.NodeRmdirer)((*Node)(nil))
+
+// 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.
+func (n *Node) Rename(ctx context.Context, oldName string, newParent fusefs.InodeEmbedder, newName string, flags uint32) (errno syscall.Errno) {
+	defer log.Trace(n, "oldName=%q, newParent=%v, newName=%q", oldName, newParent, newName)("errno=%v", &errno)
+	oldDir, ok := n.node.(*vfs.Dir)
+	if !ok {
+		return syscall.ENOTDIR
+	}
+	newParentNode, ok := newParent.(*Node)
+	if !ok {
+		fs.Errorf(n, "newParent was not a *Node")
+		return syscall.EIO
+	}
+	newDir, ok := newParentNode.node.(*vfs.Dir)
+	if !ok {
+		return syscall.ENOTDIR
+	}
+	return translateError(oldDir.Rename(oldName, newName, newDir))
+}
+
+var _ = (fusefs.NodeRenamer)((*Node)(nil))