aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZahoor Mohamed <zahoor@zahoor.in>2017-03-23 21:56:06 +0800
committerFelix Lange <fjl@users.noreply.github.com>2017-03-23 21:56:06 +0800
commit11e7a712f469fb24ddb88ecebcefab6ed8880eb8 (patch)
treec052776c80475767eb7a038bef99ff784b071ef7
parent61d2150a0750a554250c3bf090ef994be6c060f0 (diff)
downloadgo-tangerine-11e7a712f469fb24ddb88ecebcefab6ed8880eb8.tar
go-tangerine-11e7a712f469fb24ddb88ecebcefab6ed8880eb8.tar.gz
go-tangerine-11e7a712f469fb24ddb88ecebcefab6ed8880eb8.tar.bz2
go-tangerine-11e7a712f469fb24ddb88ecebcefab6ed8880eb8.tar.lz
go-tangerine-11e7a712f469fb24ddb88ecebcefab6ed8880eb8.tar.xz
go-tangerine-11e7a712f469fb24ddb88ecebcefab6ed8880eb8.tar.zst
go-tangerine-11e7a712f469fb24ddb88ecebcefab6ed8880eb8.zip
swarm/api: support mounting manifests via FUSE (#3690)
-rw-r--r--.travis.yml15
-rw-r--r--build/ci.go14
-rw-r--r--cmd/swarm/main.go2
-rw-r--r--internal/web3ext/web3ext.go29
-rw-r--r--swarm/api/fuse.go139
-rw-r--r--swarm/api/swarmfs.go48
-rw-r--r--swarm/api/swarmfs_unix.go266
-rw-r--r--swarm/api/swarmfs_unix_test.go122
-rw-r--r--swarm/api/swarmfs_windows.go48
-rw-r--r--swarm/swarm.go15
-rw-r--r--vendor/bazil.org/fuse/LICENSE93
-rw-r--r--vendor/bazil.org/fuse/README.md23
-rw-r--r--vendor/bazil.org/fuse/buffer.go35
-rw-r--r--vendor/bazil.org/fuse/debug.go21
-rw-r--r--vendor/bazil.org/fuse/error_darwin.go17
-rw-r--r--vendor/bazil.org/fuse/error_freebsd.go15
-rw-r--r--vendor/bazil.org/fuse/error_linux.go17
-rw-r--r--vendor/bazil.org/fuse/error_std.go31
-rw-r--r--vendor/bazil.org/fuse/fs/serve.go1568
-rw-r--r--vendor/bazil.org/fuse/fs/tree.go99
-rw-r--r--vendor/bazil.org/fuse/fuse.go2303
-rw-r--r--vendor/bazil.org/fuse/fuse.iml9
-rw-r--r--vendor/bazil.org/fuse/fuse_darwin.go9
-rw-r--r--vendor/bazil.org/fuse/fuse_freebsd.go6
-rw-r--r--vendor/bazil.org/fuse/fuse_kernel.go774
-rw-r--r--vendor/bazil.org/fuse/fuse_kernel_darwin.go88
-rw-r--r--vendor/bazil.org/fuse/fuse_kernel_freebsd.go62
-rw-r--r--vendor/bazil.org/fuse/fuse_kernel_linux.go70
-rw-r--r--vendor/bazil.org/fuse/fuse_kernel_std.go1
-rw-r--r--vendor/bazil.org/fuse/fuse_linux.go7
-rw-r--r--vendor/bazil.org/fuse/fuseutil/fuseutil.go20
-rw-r--r--vendor/bazil.org/fuse/mount.go38
-rw-r--r--vendor/bazil.org/fuse/mount_darwin.go208
-rw-r--r--vendor/bazil.org/fuse/mount_freebsd.go111
-rw-r--r--vendor/bazil.org/fuse/mount_linux.go150
-rw-r--r--vendor/bazil.org/fuse/options.go310
-rw-r--r--vendor/bazil.org/fuse/options_darwin.go35
-rw-r--r--vendor/bazil.org/fuse/options_freebsd.go28
-rw-r--r--vendor/bazil.org/fuse/options_linux.go25
-rw-r--r--vendor/bazil.org/fuse/protocol.go75
-rw-r--r--vendor/bazil.org/fuse/unmount.go6
-rw-r--r--vendor/bazil.org/fuse/unmount_linux.go21
-rw-r--r--vendor/bazil.org/fuse/unmount_std.go17
-rw-r--r--vendor/vendor.json18
44 files changed, 7005 insertions, 3 deletions
diff --git a/.travis.yml b/.travis.yml
index 5f3ff9d16..4bef48a0d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,19 +5,34 @@ matrix:
include:
- os: linux
dist: trusty
+ sudo: required
go: 1.7.5
+ script:
+ - sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
+ - sudo modprobe fuse
+ - sudo chmod 666 /dev/fuse
+ - sudo chown root:$USER /etc/fuse.conf
# These are the latest Go versions, only run go vet and misspell on these
- os: linux
dist: trusty
+ sudo: required
go: 1.8
script:
+ - sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
+ - sudo modprobe fuse
+ - sudo chmod 666 /dev/fuse
+ - sudo chown root:$USER /etc/fuse.conf
- go run build/ci.go install
- go run build/ci.go test -coverage -vet -misspell
- os: osx
go: 1.8
+ sudo: required
script:
+ - brew update
+ - brew install caskroom/cask/brew-cask
+ - brew cask install osxfuse
- go run build/ci.go install
- go run build/ci.go test -coverage -vet -misspell
diff --git a/build/ci.go b/build/ci.go
index cb9c7a335..27589fc7f 100644
--- a/build/ci.go
+++ b/build/ci.go
@@ -173,6 +173,20 @@ func doInstall(cmdline []string) {
if flag.NArg() > 0 {
packages = flag.Args()
}
+
+ // Resolve ./... manually and remove vendor/bazil/fuse (fuse is not in windows)
+ out, err := goTool("list", "./...").CombinedOutput()
+ if err != nil {
+ log.Fatalf("package listing failed: %v\n%s", err, string(out))
+ }
+ packages = []string{}
+ for _, line := range strings.Split(string(out), "\n") {
+ if !strings.Contains(line, "vendor") {
+ packages = append(packages, strings.TrimSpace(line))
+ }
+ }
+
+
if *arch == "" || *arch == runtime.GOARCH {
goinstall := goTool("install", buildFlags(env)...)
goinstall.Args = append(goinstall.Args, "-v")
diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go
index 171677146..3c82497e5 100644
--- a/cmd/swarm/main.go
+++ b/cmd/swarm/main.go
@@ -122,7 +122,7 @@ func init() {
// Override flag defaults so bzzd can run alongside geth.
utils.ListenPortFlag.Value = 30399
utils.IPCPathFlag.Value = utils.DirectoryString{Value: "bzzd.ipc"}
- utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, web3"
+ utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, swarmfs, web3"
// Set up the cli app.
app.Action = bzzd
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index 23112c1f1..79f781781 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -27,9 +27,12 @@ var Modules = map[string]string{
"personal": Personal_JS,
"rpc": RPC_JS,
"shh": Shh_JS,
+ "swarmfs": SWARMFS_JS,
"txpool": TxPool_JS,
+
}
+
const Chequebook_JS = `
web3._extend({
property: 'chequebook',
@@ -486,6 +489,32 @@ web3._extend({
]
});
`
+const SWARMFS_JS = `
+web3._extend({
+ property: 'swarmfs',
+ methods:
+ [
+ new web3._extend.Method({
+ name: 'mount',
+ call: 'swarmfs_mount',
+ params: 2,
+ inputFormatter: [null,null]
+ }),
+ new web3._extend.Method({
+ name: 'unmount',
+ call: 'swarmfs_unmount',
+ params: 1,
+ inputFormatter: [null]
+ }),
+ new web3._extend.Method({
+ name: 'listmounts',
+ call: 'swarmfs_listmounts',
+ params: 0,
+ inputFormatter: []
+ })
+ ]
+});
+`
const TxPool_JS = `
web3._extend({
diff --git a/swarm/api/fuse.go b/swarm/api/fuse.go
new file mode 100644
index 000000000..4b1f817f8
--- /dev/null
+++ b/swarm/api/fuse.go
@@ -0,0 +1,139 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// +build !windows
+
+package api
+
+import (
+ "io"
+ "os"
+
+ "bazil.org/fuse"
+ "bazil.org/fuse/fs"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/swarm/storage"
+ "golang.org/x/net/context"
+)
+
+
+
+
+// Data structures used for Fuse filesystem, serving directories and serving files to Fuse driver
+type FS struct {
+ root *Dir
+}
+
+type Dir struct {
+ inode uint64
+ name string
+ path string
+ directories []*Dir
+ files []*File
+}
+
+type File struct {
+ inode uint64
+ name string
+ path string
+ key storage.Key
+ swarmApi *Api
+ fileSize uint64
+ reader storage.LazySectionReader
+}
+
+
+// Functions which satisfy the Fuse File System requests
+func (filesystem *FS) Root() (fs.Node, error) {
+ return filesystem.root, nil
+}
+
+func (directory *Dir) Attr(ctx context.Context, a *fuse.Attr) error {
+ a.Inode = directory.inode
+ //TODO: need to get permission as argument
+ a.Mode = os.ModeDir | 0500
+ a.Uid = uint32(os.Getuid())
+ a.Gid = uint32(os.Getegid())
+ return nil
+}
+
+func (directory *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
+ if directory.files != nil {
+ for _, n := range directory.files {
+ if n.name == name {
+ return n, nil
+ }
+ }
+ }
+ if directory.directories != nil {
+ for _, n := range directory.directories {
+ if n.name == name {
+ return n, nil
+ }
+ }
+ }
+ return nil, fuse.ENOENT
+}
+
+func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
+ var children []fuse.Dirent
+ if d.files != nil {
+ for _, file := range d.files {
+ children = append(children, fuse.Dirent{Inode: file.inode, Type: fuse.DT_File, Name: file.name})
+ }
+ }
+ if d.directories != nil {
+ for _, dir := range d.directories {
+ children = append(children, fuse.Dirent{Inode: dir.inode, Type: fuse.DT_Dir, Name: dir.name})
+ }
+ }
+ return children, nil
+}
+
+func (file *File) Attr(ctx context.Context, a *fuse.Attr) error {
+
+ a.Inode = file.inode
+ //TODO: need to get permission as argument
+ a.Mode = 0500
+ a.Uid = uint32(os.Getuid())
+ a.Gid = uint32(os.Getegid())
+
+
+ reader := file.swarmApi.Retrieve(file.key)
+ quitC := make(chan bool)
+ size, err := reader.Size(quitC)
+ if err != nil {
+ log.Warn("Couldnt file size of file %s : %v", file.path, err)
+ a.Size = uint64(0)
+ }
+ a.Size = uint64(size)
+ file.fileSize = a.Size
+ return nil
+}
+
+var _ = fs.HandleReader(&File{})
+
+func (file *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
+ buf := make([]byte, req.Size)
+ reader := file.swarmApi.Retrieve(file.key)
+ n, err := reader.ReadAt(buf, req.Offset)
+ if err == io.ErrUnexpectedEOF || err == io.EOF {
+ err = nil
+ }
+ resp.Data = buf[:n]
+ return err
+
+}
diff --git a/swarm/api/swarmfs.go b/swarm/api/swarmfs.go
new file mode 100644
index 000000000..8427d3c5b
--- /dev/null
+++ b/swarm/api/swarmfs.go
@@ -0,0 +1,48 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package api
+
+import (
+ "time"
+ "sync"
+)
+
+const (
+ Swarmfs_Version = "0.1"
+ mountTimeout = time.Second * 5
+ maxFuseMounts = 5
+)
+
+
+type SwarmFS struct {
+ swarmApi *Api
+ activeMounts map[string]*MountInfo
+ activeLock *sync.RWMutex
+}
+
+
+
+func NewSwarmFS(api *Api) *SwarmFS {
+ swarmfs := &SwarmFS{
+ swarmApi: api,
+ activeLock: &sync.RWMutex{},
+ activeMounts: map[string]*MountInfo{},
+ }
+ return swarmfs
+}
+
+
diff --git a/swarm/api/swarmfs_unix.go b/swarm/api/swarmfs_unix.go
new file mode 100644
index 000000000..ae0216e1d
--- /dev/null
+++ b/swarm/api/swarmfs_unix.go
@@ -0,0 +1,266 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// +build linux darwin
+
+package api
+
+import (
+ "path/filepath"
+ "fmt"
+ "strings"
+ "time"
+ "github.com/ethereum/go-ethereum/swarm/storage"
+ "bazil.org/fuse"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/common"
+ "bazil.org/fuse/fs"
+ "sync"
+)
+
+
+var (
+ inode uint64 = 1
+ inodeLock sync.RWMutex
+)
+
+// information about every active mount
+type MountInfo struct {
+ mountPoint string
+ manifestHash string
+ resolvedKey storage.Key
+ rootDir *Dir
+ fuseConnection *fuse.Conn
+}
+
+// Inode numbers need to be unique, they are used for caching inside fuse
+func NewInode() uint64 {
+ inodeLock.Lock()
+ defer inodeLock.Unlock()
+ inode += 1
+ return inode
+}
+
+
+
+func (self *SwarmFS) Mount(mhash, mountpoint string) (string, error) {
+
+ self.activeLock.Lock()
+ defer self.activeLock.Unlock()
+
+ noOfActiveMounts := len(self.activeMounts)
+ if noOfActiveMounts >= maxFuseMounts {
+ err := fmt.Errorf("Max mount count reached. Cannot mount %s ", mountpoint)
+ log.Warn(err.Error())
+ return err.Error(), err
+ }
+
+ cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint))
+ if err != nil {
+ return err.Error(), err
+ }
+
+ if _, ok := self.activeMounts[cleanedMountPoint]; ok {
+ err := fmt.Errorf("Mountpoint %s already mounted.", cleanedMountPoint)
+ log.Warn(err.Error())
+ return err.Error(), err
+ }
+
+ log.Info(fmt.Sprintf("Attempting to mount %s ", cleanedMountPoint))
+ key, _, path, err := self.swarmApi.parseAndResolve(mhash, true)
+ if err != nil {
+ errStr := fmt.Sprintf("Could not resolve %s : %v", mhash, err)
+ log.Warn(errStr)
+ return errStr, err
+ }
+
+ if len(path) > 0 {
+ path += "/"
+ }
+
+ quitC := make(chan bool)
+ trie, err := loadManifest(self.swarmApi.dpa, key, quitC)
+ if err != nil {
+ errStr := fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)
+ log.Warn(errStr)
+ return errStr, err
+ }
+
+ dirTree := map[string]*Dir{}
+
+ rootDir := &Dir{
+ inode: NewInode(),
+ name: "root",
+ directories: nil,
+ files: nil,
+ }
+ dirTree["root"] = rootDir
+
+ err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) {
+
+ key = common.Hex2Bytes(entry.Hash)
+ fullpath := "/" + suffix
+ basepath := filepath.Dir(fullpath)
+ filename := filepath.Base(fullpath)
+
+ parentDir := rootDir
+ dirUntilNow := ""
+ paths := strings.Split(basepath, "/")
+ for i := range paths {
+ if paths[i] != "" {
+ thisDir := paths[i]
+ dirUntilNow = dirUntilNow + "/" + thisDir
+
+ if _, ok := dirTree[dirUntilNow]; !ok {
+ dirTree[dirUntilNow] = &Dir{
+ inode: NewInode(),
+ name: thisDir,
+ path: dirUntilNow,
+ directories: nil,
+ files: nil,
+ }
+ parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow])
+ parentDir = dirTree[dirUntilNow]
+
+ } else {
+ parentDir = dirTree[dirUntilNow]
+ }
+
+ }
+ }
+ thisFile := &File{
+ inode: NewInode(),
+ name: filename,
+ path: fullpath,
+ key: key,
+ swarmApi: self.swarmApi,
+ }
+ parentDir.files = append(parentDir.files, thisFile)
+ })
+
+ fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash))
+ if err != nil {
+ fuse.Unmount(cleanedMountPoint)
+ errStr := fmt.Sprintf("Mounting %s encountered error: %v", cleanedMountPoint, err)
+ log.Warn(errStr)
+ return errStr, err
+ }
+
+ mounterr := make(chan error, 1)
+ go func() {
+ log.Info(fmt.Sprintf("Serving %s at %s", mhash, cleanedMountPoint))
+ filesys := &FS{root: rootDir}
+ if err := fs.Serve(fconn, filesys); err != nil {
+ log.Warn(fmt.Sprintf("Could not Serve FS error: %v", err))
+ }
+ }()
+
+ // Check if the mount process has an error to report.
+ select {
+
+ case <-time.After(mountTimeout):
+ err := fmt.Errorf("Mounting %s timed out.", cleanedMountPoint)
+ log.Warn(err.Error())
+ return err.Error(), err
+
+ case err := <-mounterr:
+ errStr := fmt.Sprintf("Mounting %s encountered error: %v", cleanedMountPoint, err)
+ log.Warn(errStr)
+ return errStr, err
+
+ case <-fconn.Ready:
+ log.Debug(fmt.Sprintf("Mounting connection succeeded for : %v", cleanedMountPoint))
+ }
+
+
+
+ //Assemble and Store the mount information for future use
+ mountInformation := &MountInfo{
+ mountPoint: cleanedMountPoint,
+ manifestHash: mhash,
+ resolvedKey: key,
+ rootDir: rootDir,
+ fuseConnection: fconn,
+ }
+ self.activeMounts[cleanedMountPoint] = mountInformation
+
+ succString := fmt.Sprintf("Mounting successful for %s", cleanedMountPoint)
+ log.Info(succString)
+
+ return succString, nil
+}
+
+func (self *SwarmFS) Unmount(mountpoint string) (string, error) {
+
+ self.activeLock.Lock()
+ defer self.activeLock.Unlock()
+
+ cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint))
+ if err != nil {
+ return err.Error(), err
+ }
+
+ // Get the mount information based on the mountpoint argument
+ mountInfo := self.activeMounts[cleanedMountPoint]
+
+
+ if mountInfo == nil || mountInfo.mountPoint != cleanedMountPoint {
+ err := fmt.Errorf("Could not find mount information for %s ", cleanedMountPoint)
+ log.Warn(err.Error())
+ return err.Error(), err
+ }
+
+ err = fuse.Unmount(cleanedMountPoint)
+ if err != nil {
+ //TODO: try forceful unmount if normal unmount fails
+ errStr := fmt.Sprintf("UnMount error: %v", err)
+ log.Warn(errStr)
+ return errStr, err
+ }
+
+ mountInfo.fuseConnection.Close()
+
+ //remove the mount information from the active map
+ delete(self.activeMounts, cleanedMountPoint)
+
+ succString := fmt.Sprintf("UnMounting %v succeeded", cleanedMountPoint)
+ log.Info(succString)
+ return succString, nil
+}
+
+func (self *SwarmFS) Listmounts() (string, error) {
+
+ self.activeLock.RLock()
+ defer self.activeLock.RUnlock()
+
+ var rows []string
+ for mp := range self.activeMounts {
+ mountInfo := self.activeMounts[mp]
+ rows = append(rows, fmt.Sprintf("Swarm Root: %s, Mount Point: %s ", mountInfo.manifestHash, mountInfo.mountPoint))
+ }
+
+ return strings.Join(rows, "\n"), nil
+}
+
+func (self *SwarmFS) Stop() bool {
+
+ for mp := range self.activeMounts {
+ mountInfo := self.activeMounts[mp]
+ self.Unmount(mountInfo.mountPoint)
+ }
+
+ return true
+}
diff --git a/swarm/api/swarmfs_unix_test.go b/swarm/api/swarmfs_unix_test.go
new file mode 100644
index 000000000..4f59dba5b
--- /dev/null
+++ b/swarm/api/swarmfs_unix_test.go
@@ -0,0 +1,122 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// +build linux darwin
+
+package api
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+var testUploadDir, _ = ioutil.TempDir(os.TempDir(), "fuse-source")
+var testMountDir, _ = ioutil.TempDir(os.TempDir(), "fuse-dest")
+
+func testFuseFileSystem(t *testing.T, f func(*FileSystem)) {
+ testApi(t, func(api *Api) {
+ f(NewFileSystem(api))
+ })
+}
+
+func createTestFiles(t *testing.T, files []string) {
+
+ os.RemoveAll(testUploadDir)
+ os.RemoveAll(testMountDir)
+ defer os.MkdirAll(testMountDir, 0777)
+
+ for f := range files {
+ actualPath := filepath.Join(testUploadDir, files[f])
+ filePath := filepath.Dir(actualPath)
+
+ err := os.MkdirAll(filePath, 0777)
+ if err != nil {
+ t.Fatalf("Error creating directory '%v' : %v", filePath, err)
+ }
+
+ _, err1 := os.OpenFile(actualPath, os.O_RDONLY|os.O_CREATE, 0666)
+ if err1 != nil {
+ t.Fatalf("Error creating file %v: %v", actualPath, err1)
+ }
+ }
+
+}
+
+func compareFiles(t *testing.T, files []string) {
+
+ for f := range files {
+
+ sourceFile := filepath.Join(testUploadDir, files[f])
+ destinationFile := filepath.Join(testMountDir, files[f])
+
+ sfinfo, err := os.Stat(sourceFile)
+ if err != nil {
+ t.Fatalf("Source file %v missing in mount: %v", files[f], err)
+ }
+
+ dfinfo, err := os.Stat(destinationFile)
+ if err != nil {
+ t.Fatalf("Destination file %v missing in mount: %v", files[f], err)
+ }
+
+ if sfinfo.Size() != dfinfo.Size() {
+ t.Fatalf("Size mismatch source (%v) vs destination(%v)", sfinfo.Size(), dfinfo.Size())
+ }
+
+ if dfinfo.Mode().Perm().String() != "-r-x------" {
+ t.Fatalf("Permission is not 0500for file: %v", err)
+ }
+
+ }
+}
+
+func doHashTest(fs *FileSystem, t *testing.T, ensName string, files ...string) {
+
+ createTestFiles(t, files)
+ bzzhash, err := fs.Upload(testUploadDir, "")
+ if err != nil {
+ t.Fatalf("Error uploading directory %v: %v", testUploadDir, err)
+ }
+
+ swarmfs := NewSwarmFS(fs.api)
+ _ ,err1 := swarmfs.Mount(bzzhash, testMountDir)
+ if err1 != nil {
+
+ t.Fatalf("Error mounting hash %v: %v", bzzhash, err)
+ }
+ compareFiles(t, files)
+ _, err2 := swarmfs.Unmount(testMountDir)
+ if err2 != nil {
+ t.Fatalf("Error unmounting path %v: %v", testMountDir, err)
+ }
+ swarmfs.Stop()
+
+}
+
+// mounting with manifest Hash
+func TestFuseMountingScenarios(t *testing.T) {
+ testFuseFileSystem(t, func(fs *FileSystem) {
+
+ //doHashTest(fs,t, "test","1.txt")
+ doHashTest(fs, t, "", "1.txt")
+ doHashTest(fs, t, "", "1.txt", "11.txt", "111.txt", "two/2.txt", "two/two/2.txt", "three/3.txt")
+ doHashTest(fs, t, "", "1/2/3/4/5/6/7/8/9/10/11/12/1.txt")
+ doHashTest(fs, t, "", "one/one.txt", "one.txt", "once/one.txt", "one/one/one.txt")
+
+ })
+}
diff --git a/swarm/api/swarmfs_windows.go b/swarm/api/swarmfs_windows.go
new file mode 100644
index 000000000..525a25399
--- /dev/null
+++ b/swarm/api/swarmfs_windows.go
@@ -0,0 +1,48 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// +build windows
+
+package api
+
+import (
+ "github.com/ethereum/go-ethereum/log"
+)
+
+// Dummy struct and functions to satsfy windows build
+type MountInfo struct {
+}
+
+
+func (self *SwarmFS) Mount(mhash, mountpoint string) error {
+ log.Info("Platform not supported")
+ return nil
+}
+
+func (self *SwarmFS) Unmount(mountpoint string) error {
+ log.Info("Platform not supported")
+ return nil
+}
+
+func (self *SwarmFS) Listmounts() (string, error) {
+ log.Info("Platform not supported")
+ return "",nil
+}
+
+func (self *SwarmFS) Stop() error {
+ log.Info("Platform not supported")
+ return nil
+} \ No newline at end of file
diff --git a/swarm/swarm.go b/swarm/swarm.go
index bd256edaa..0ce31bcad 100644
--- a/swarm/swarm.go
+++ b/swarm/swarm.go
@@ -53,7 +53,8 @@ type Swarm struct {
privateKey *ecdsa.PrivateKey
corsString string
swapEnabled bool
- lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped
+ lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped
+ sfs *api.SwarmFS // need this to cleanup all the active mounts on node exit
}
type SwarmAPI struct {
@@ -142,6 +143,9 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
// Manifests for Smart Hosting
log.Debug(fmt.Sprintf("-> Web3 virtual server API"))
+ self.sfs = api.NewSwarmFS(self.api)
+ log.Debug("-> Initializing Fuse file system")
+
return self, nil
}
@@ -216,7 +220,7 @@ func (self *Swarm) Stop() error {
if self.lstore != nil {
self.lstore.DbStore.Close()
}
-
+ self.sfs.Stop()
return self.config.Save()
}
@@ -240,6 +244,7 @@ func (self *Swarm) APIs() []rpc.API {
Service: api.NewStorage(self.api),
Public: true,
},
+
{
Namespace: "bzz",
Version: "0.1",
@@ -264,6 +269,12 @@ func (self *Swarm) APIs() []rpc.API {
Service: chequebook.NewApi(self.config.Swap.Chequebook),
Public: false,
},
+ {
+ Namespace: "swarmfs",
+ Version: api.Swarmfs_Version,
+ Service: self.sfs,
+ Public: false,
+ },
// {Namespace, Version, api.NewAdmin(self), false},
}
}
diff --git a/vendor/bazil.org/fuse/LICENSE b/vendor/bazil.org/fuse/LICENSE
new file mode 100644
index 000000000..4ac7cd838
--- /dev/null
+++ b/vendor/bazil.org/fuse/LICENSE
@@ -0,0 +1,93 @@
+Copyright (c) 2013-2015 Tommi Virtanen.
+Copyright (c) 2009, 2011, 2012 The Go Authors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+The following included software components have additional copyright
+notices and license terms that may differ from the above.
+
+
+File fuse.go:
+
+// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
+// which carries this notice:
+//
+// The files in this directory are subject to the following license.
+//
+// The author of this software is Russ Cox.
+//
+// Copyright (c) 2006 Russ Cox
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose without fee is hereby granted, provided that this entire notice
+// is included in all copies of any software which is or includes a copy
+// or modification of this software and in all copies of the supporting
+// documentation for such software.
+//
+// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
+// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
+// FITNESS FOR ANY PARTICULAR PURPOSE.
+
+
+File fuse_kernel.go:
+
+// Derived from FUSE's fuse_kernel.h
+/*
+ This file defines the kernel interface of FUSE
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
+ This -- and only this -- header file may also be distributed under
+ the terms of the BSD Licence as follows:
+
+ Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+*/
diff --git a/vendor/bazil.org/fuse/README.md b/vendor/bazil.org/fuse/README.md
new file mode 100644
index 000000000..8c6d556ee
--- /dev/null
+++ b/vendor/bazil.org/fuse/README.md
@@ -0,0 +1,23 @@
+bazil.org/fuse -- Filesystems in Go
+===================================
+
+`bazil.org/fuse` is a Go library for writing FUSE userspace
+filesystems.
+
+It is a from-scratch implementation of the kernel-userspace
+communication protocol, and does not use the C library from the
+project called FUSE. `bazil.org/fuse` embraces Go fully for safety and
+ease of programming.
+
+Here’s how to get going:
+
+ go get bazil.org/fuse
+
+Website: http://bazil.org/fuse/
+
+Github repository: https://github.com/bazil/fuse
+
+API docs: http://godoc.org/bazil.org/fuse
+
+Our thanks to Russ Cox for his fuse library, which this project is
+based on.
diff --git a/vendor/bazil.org/fuse/buffer.go b/vendor/bazil.org/fuse/buffer.go
new file mode 100644
index 000000000..bb1d2b776
--- /dev/null
+++ b/vendor/bazil.org/fuse/buffer.go
@@ -0,0 +1,35 @@
+package fuse
+
+import "unsafe"
+
+// buffer provides a mechanism for constructing a message from
+// multiple segments.
+type buffer []byte
+
+// alloc allocates size bytes and returns a pointer to the new
+// segment.
+func (w *buffer) alloc(size uintptr) unsafe.Pointer {
+ s := int(size)
+ if len(*w)+s > cap(*w) {
+ old := *w
+ *w = make([]byte, len(*w), 2*cap(*w)+s)
+ copy(*w, old)
+ }
+ l := len(*w)
+ *w = (*w)[:l+s]
+ return unsafe.Pointer(&(*w)[l])
+}
+
+// reset clears out the contents of the buffer.
+func (w *buffer) reset() {
+ for i := range (*w)[:cap(*w)] {
+ (*w)[i] = 0
+ }
+ *w = (*w)[:0]
+}
+
+func newBuffer(extra uintptr) buffer {
+ const hdrSize = unsafe.Sizeof(outHeader{})
+ buf := make(buffer, hdrSize, hdrSize+extra)
+ return buf
+}
diff --git a/vendor/bazil.org/fuse/debug.go b/vendor/bazil.org/fuse/debug.go
new file mode 100644
index 000000000..be9f900d5
--- /dev/null
+++ b/vendor/bazil.org/fuse/debug.go
@@ -0,0 +1,21 @@
+package fuse
+
+import (
+ "runtime"
+)
+
+func stack() string {
+ buf := make([]byte, 1024)
+ return string(buf[:runtime.Stack(buf, false)])
+}
+
+func nop(msg interface{}) {}
+
+// Debug is called to output debug messages, including protocol
+// traces. The default behavior is to do nothing.
+//
+// The messages have human-friendly string representations and are
+// safe to marshal to JSON.
+//
+// Implementations must not retain msg.
+var Debug func(msg interface{}) = nop
diff --git a/vendor/bazil.org/fuse/error_darwin.go b/vendor/bazil.org/fuse/error_darwin.go
new file mode 100644
index 000000000..a3fb89ca2
--- /dev/null
+++ b/vendor/bazil.org/fuse/error_darwin.go
@@ -0,0 +1,17 @@
+package fuse
+
+import (
+ "syscall"
+)
+
+const (
+ ENOATTR = Errno(syscall.ENOATTR)
+)
+
+const (
+ errNoXattr = ENOATTR
+)
+
+func init() {
+ errnoNames[errNoXattr] = "ENOATTR"
+}
diff --git a/vendor/bazil.org/fuse/error_freebsd.go b/vendor/bazil.org/fuse/error_freebsd.go
new file mode 100644
index 000000000..c6ea6d6e7
--- /dev/null
+++ b/vendor/bazil.org/fuse/error_freebsd.go
@@ -0,0 +1,15 @@
+package fuse
+
+import "syscall"
+
+const (
+ ENOATTR = Errno(syscall.ENOATTR)
+)
+
+const (
+ errNoXattr = ENOATTR
+)
+
+func init() {
+ errnoNames[errNoXattr] = "ENOATTR"
+}
diff --git a/vendor/bazil.org/fuse/error_linux.go b/vendor/bazil.org/fuse/error_linux.go
new file mode 100644
index 000000000..6f113e71e
--- /dev/null
+++ b/vendor/bazil.org/fuse/error_linux.go
@@ -0,0 +1,17 @@
+package fuse
+
+import (
+ "syscall"
+)
+
+const (
+ ENODATA = Errno(syscall.ENODATA)
+)
+
+const (
+ errNoXattr = ENODATA
+)
+
+func init() {
+ errnoNames[errNoXattr] = "ENODATA"
+}
diff --git a/vendor/bazil.org/fuse/error_std.go b/vendor/bazil.org/fuse/error_std.go
new file mode 100644
index 000000000..398f43fbf
--- /dev/null
+++ b/vendor/bazil.org/fuse/error_std.go
@@ -0,0 +1,31 @@
+package fuse
+
+// There is very little commonality in extended attribute errors
+// across platforms.
+//
+// getxattr return value for "extended attribute does not exist" is
+// ENOATTR on OS X, and ENODATA on Linux and apparently at least
+// NetBSD. There may be a #define ENOATTR on Linux too, but the value
+// is ENODATA in the actual syscalls. FreeBSD and OpenBSD have no
+// ENODATA, only ENOATTR. ENOATTR is not in any of the standards,
+// ENODATA exists but is only used for STREAMs.
+//
+// Each platform will define it a errNoXattr constant, and this file
+// will enforce that it implements the right interfaces and hide the
+// implementation.
+//
+// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getxattr.2.html
+// http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013090.html
+// http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013097.html
+// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
+// http://www.freebsd.org/cgi/man.cgi?query=extattr_get_file&sektion=2
+// http://nixdoc.net/man-pages/openbsd/man2/extattr_get_file.2.html
+
+// ErrNoXattr is a platform-independent error value meaning the
+// extended attribute was not found. It can be used to respond to
+// GetxattrRequest and such.
+const ErrNoXattr = errNoXattr
+
+var _ error = ErrNoXattr
+var _ Errno = ErrNoXattr
+var _ ErrorNumber = ErrNoXattr
diff --git a/vendor/bazil.org/fuse/fs/serve.go b/vendor/bazil.org/fuse/fs/serve.go
new file mode 100644
index 000000000..e9fc56590
--- /dev/null
+++ b/vendor/bazil.org/fuse/fs/serve.go
@@ -0,0 +1,1568 @@
+// FUSE service loop, for servers that wish to use it.
+
+package fs // import "bazil.org/fuse/fs"
+
+import (
+ "encoding/binary"
+ "fmt"
+ "hash/fnv"
+ "io"
+ "log"
+ "reflect"
+ "runtime"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/net/context"
+)
+
+import (
+ "bytes"
+
+ "bazil.org/fuse"
+ "bazil.org/fuse/fuseutil"
+)
+
+const (
+ attrValidTime = 1 * time.Minute
+ entryValidTime = 1 * time.Minute
+)
+
+// TODO: FINISH DOCS
+
+// An FS is the interface required of a file system.
+//
+// Other FUSE requests can be handled by implementing methods from the
+// FS* interfaces, for example FSStatfser.
+type FS interface {
+ // Root is called to obtain the Node for the file system root.
+ Root() (Node, error)
+}
+
+type FSStatfser interface {
+ // Statfs is called to obtain file system metadata.
+ // It should write that data to resp.
+ Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error
+}
+
+type FSDestroyer interface {
+ // Destroy is called when the file system is shutting down.
+ //
+ // Linux only sends this request for block device backed (fuseblk)
+ // filesystems, to allow them to flush writes to disk before the
+ // unmount completes.
+ Destroy()
+}
+
+type FSInodeGenerator interface {
+ // GenerateInode is called to pick a dynamic inode number when it
+ // would otherwise be 0.
+ //
+ // Not all filesystems bother tracking inodes, but FUSE requires
+ // the inode to be set, and fewer duplicates in general makes UNIX
+ // tools work better.
+ //
+ // Operations where the nodes may return 0 inodes include Getattr,
+ // Setattr and ReadDir.
+ //
+ // If FS does not implement FSInodeGenerator, GenerateDynamicInode
+ // is used.
+ //
+ // Implementing this is useful to e.g. constrain the range of
+ // inode values used for dynamic inodes.
+ GenerateInode(parentInode uint64, name string) uint64
+}
+
+// A Node is the interface required of a file or directory.
+// See the documentation for type FS for general information
+// pertaining to all methods.
+//
+// A Node must be usable as a map key, that is, it cannot be a
+// function, map or slice.
+//
+// Other FUSE requests can be handled by implementing methods from the
+// Node* interfaces, for example NodeOpener.
+//
+// Methods returning Node should take care to return the same Node
+// when the result is logically the same instance. Without this, each
+// Node will get a new NodeID, causing spurious cache invalidations,
+// extra lookups and aliasing anomalies. This may not matter for a
+// simple, read-only filesystem.
+type Node interface {
+ // Attr fills attr with the standard metadata for the node.
+ //
+ // Fields with reasonable defaults are prepopulated. For example,
+ // all times are set to a fixed moment when the program started.
+ //
+ // If Inode is left as 0, a dynamic inode number is chosen.
+ //
+ // The result may be cached for the duration set in Valid.
+ Attr(ctx context.Context, attr *fuse.Attr) error
+}
+
+type NodeGetattrer interface {
+ // Getattr obtains the standard metadata for the receiver.
+ // It should store that metadata in resp.
+ //
+ // If this method is not implemented, the attributes will be
+ // generated based on Attr(), with zero values filled in.
+ Getattr(ctx context.Context, req *fuse.GetattrRequest, resp *fuse.GetattrResponse) error
+}
+
+type NodeSetattrer interface {
+ // Setattr sets the standard metadata for the receiver.
+ //
+ // Note, this is also used to communicate changes in the size of
+ // the file, outside of Writes.
+ //
+ // req.Valid is a bitmask of what fields are actually being set.
+ // For example, the method should not change the mode of the file
+ // unless req.Valid.Mode() is true.
+ Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error
+}
+
+type NodeSymlinker interface {
+ // Symlink creates a new symbolic link in the receiver, which must be a directory.
+ //
+ // TODO is the above true about directories?
+ Symlink(ctx context.Context, req *fuse.SymlinkRequest) (Node, error)
+}
+
+// This optional request will be called only for symbolic link nodes.
+type NodeReadlinker interface {
+ // Readlink reads a symbolic link.
+ Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error)
+}
+
+type NodeLinker interface {
+ // Link creates a new directory entry in the receiver based on an
+ // existing Node. Receiver must be a directory.
+ Link(ctx context.Context, req *fuse.LinkRequest, old Node) (Node, error)
+}
+
+type NodeRemover interface {
+ // Remove removes the entry with the given name from
+ // the receiver, which must be a directory. The entry to be removed
+ // may correspond to a file (unlink) or to a directory (rmdir).
+ Remove(ctx context.Context, req *fuse.RemoveRequest) error
+}
+
+type NodeAccesser interface {
+ // Access checks whether the calling context has permission for
+ // the given operations on the receiver. If so, Access should
+ // return nil. If not, Access should return EPERM.
+ //
+ // Note that this call affects the result of the access(2) system
+ // call but not the open(2) system call. If Access is not
+ // implemented, the Node behaves as if it always returns nil
+ // (permission granted), relying on checks in Open instead.
+ Access(ctx context.Context, req *fuse.AccessRequest) error
+}
+
+type NodeStringLookuper interface {
+ // Lookup looks up a specific entry in the receiver,
+ // which must be a directory. Lookup should return a Node
+ // corresponding to the entry. If the name does not exist in
+ // the directory, Lookup should return ENOENT.
+ //
+ // Lookup need not to handle the names "." and "..".
+ Lookup(ctx context.Context, name string) (Node, error)
+}
+
+type NodeRequestLookuper interface {
+ // Lookup looks up a specific entry in the receiver.
+ // See NodeStringLookuper for more.
+ Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (Node, error)
+}
+
+type NodeMkdirer interface {
+ Mkdir(ctx context.Context, req *fuse.MkdirRequest) (Node, error)
+}
+
+type NodeOpener interface {
+ // Open opens the receiver. After a successful open, a client
+ // process has a file descriptor referring to this Handle.
+ //
+ // Open can also be also called on non-files. For example,
+ // directories are Opened for ReadDir or fchdir(2).
+ //
+ // If this method is not implemented, the open will always
+ // succeed, and the Node itself will be used as the Handle.
+ //
+ // XXX note about access. XXX OpenFlags.
+ Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (Handle, error)
+}
+
+type NodeCreater interface {
+ // Create creates a new directory entry in the receiver, which
+ // must be a directory.
+ Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (Node, Handle, error)
+}
+
+type NodeForgetter interface {
+ // Forget about this node. This node will not receive further
+ // method calls.
+ //
+ // Forget is not necessarily seen on unmount, as all nodes are
+ // implicitly forgotten as part part of the unmount.
+ Forget()
+}
+
+type NodeRenamer interface {
+ Rename(ctx context.Context, req *fuse.RenameRequest, newDir Node) error
+}
+
+type NodeMknoder interface {
+ Mknod(ctx context.Context, req *fuse.MknodRequest) (Node, error)
+}
+
+// TODO this should be on Handle not Node
+type NodeFsyncer interface {
+ Fsync(ctx context.Context, req *fuse.FsyncRequest) error
+}
+
+type NodeGetxattrer interface {
+ // Getxattr gets an extended attribute by the given name from the
+ // node.
+ //
+ // If there is no xattr by that name, returns fuse.ErrNoXattr.
+ Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error
+}
+
+type NodeListxattrer interface {
+ // Listxattr lists the extended attributes recorded for the node.
+ Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error
+}
+
+type NodeSetxattrer interface {
+ // Setxattr sets an extended attribute with the given name and
+ // value for the node.
+ Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error
+}
+
+type NodeRemovexattrer interface {
+ // Removexattr removes an extended attribute for the name.
+ //
+ // If there is no xattr by that name, returns fuse.ErrNoXattr.
+ Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error
+}
+
+var startTime = time.Now()
+
+func nodeAttr(ctx context.Context, n Node, attr *fuse.Attr) error {
+ attr.Valid = attrValidTime
+ attr.Nlink = 1
+ attr.Atime = startTime
+ attr.Mtime = startTime
+ attr.Ctime = startTime
+ attr.Crtime = startTime
+ if err := n.Attr(ctx, attr); err != nil {
+ return err
+ }
+ return nil
+}
+
+// A Handle is the interface required of an opened file or directory.
+// See the documentation for type FS for general information
+// pertaining to all methods.
+//
+// Other FUSE requests can be handled by implementing methods from the
+// Handle* interfaces. The most common to implement are HandleReader,
+// HandleReadDirer, and HandleWriter.
+//
+// TODO implement methods: Getlk, Setlk, Setlkw
+type Handle interface {
+}
+
+type HandleFlusher interface {
+ // Flush is called each time the file or directory is closed.
+ // Because there can be multiple file descriptors referring to a
+ // single opened file, Flush can be called multiple times.
+ Flush(ctx context.Context, req *fuse.FlushRequest) error
+}
+
+type HandleReadAller interface {
+ ReadAll(ctx context.Context) ([]byte, error)
+}
+
+type HandleReadDirAller interface {
+ ReadDirAll(ctx context.Context) ([]fuse.Dirent, error)
+}
+
+type HandleReader interface {
+ // Read requests to read data from the handle.
+ //
+ // There is a page cache in the kernel that normally submits only
+ // page-aligned reads spanning one or more pages. However, you
+ // should not rely on this. To see individual requests as
+ // submitted by the file system clients, set OpenDirectIO.
+ //
+ // Note that reads beyond the size of the file as reported by Attr
+ // are not even attempted (except in OpenDirectIO mode).
+ Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error
+}
+
+type HandleWriter interface {
+ // Write requests to write data into the handle at the given offset.
+ // Store the amount of data written in resp.Size.
+ //
+ // There is a writeback page cache in the kernel that normally submits
+ // only page-aligned writes spanning one or more pages. However,
+ // you should not rely on this. To see individual requests as
+ // submitted by the file system clients, set OpenDirectIO.
+ //
+ // Writes that grow the file are expected to update the file size
+ // (as seen through Attr). Note that file size changes are
+ // communicated also through Setattr.
+ Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error
+}
+
+type HandleReleaser interface {
+ Release(ctx context.Context, req *fuse.ReleaseRequest) error
+}
+
+type Config struct {
+ // Function to send debug log messages to. If nil, use fuse.Debug.
+ // Note that changing this or fuse.Debug may not affect existing
+ // calls to Serve.
+ //
+ // See fuse.Debug for the rules that log functions must follow.
+ Debug func(msg interface{})
+
+ // Function to put things into context for processing the request.
+ // The returned context must have ctx as its parent.
+ //
+ // Note that changing this may not affect existing calls to Serve.
+ //
+ // Must not retain req.
+ WithContext func(ctx context.Context, req fuse.Request) context.Context
+}
+
+// New returns a new FUSE server ready to serve this kernel FUSE
+// connection.
+//
+// Config may be nil.
+func New(conn *fuse.Conn, config *Config) *Server {
+ s := &Server{
+ conn: conn,
+ req: map[fuse.RequestID]*serveRequest{},
+ nodeRef: map[Node]fuse.NodeID{},
+ dynamicInode: GenerateDynamicInode,
+ }
+ if config != nil {
+ s.debug = config.Debug
+ s.context = config.WithContext
+ }
+ if s.debug == nil {
+ s.debug = fuse.Debug
+ }
+ return s
+}
+
+type Server struct {
+ // set in New
+ conn *fuse.Conn
+ debug func(msg interface{})
+ context func(ctx context.Context, req fuse.Request) context.Context
+
+ // set once at Serve time
+ fs FS
+ dynamicInode func(parent uint64, name string) uint64
+
+ // state, protected by meta
+ meta sync.Mutex
+ req map[fuse.RequestID]*serveRequest
+ node []*serveNode
+ nodeRef map[Node]fuse.NodeID
+ handle []*serveHandle
+ freeNode []fuse.NodeID
+ freeHandle []fuse.HandleID
+ nodeGen uint64
+
+ // Used to ensure worker goroutines finish before Serve returns
+ wg sync.WaitGroup
+}
+
+// Serve serves the FUSE connection by making calls to the methods
+// of fs and the Nodes and Handles it makes available. It returns only
+// when the connection has been closed or an unexpected error occurs.
+func (s *Server) Serve(fs FS) error {
+ defer s.wg.Wait() // Wait for worker goroutines to complete before return
+
+ s.fs = fs
+ if dyn, ok := fs.(FSInodeGenerator); ok {
+ s.dynamicInode = dyn.GenerateInode
+ }
+
+ root, err := fs.Root()
+ if err != nil {
+ return fmt.Errorf("cannot obtain root node: %v", err)
+ }
+ // Recognize the root node if it's ever returned from Lookup,
+ // passed to Invalidate, etc.
+ s.nodeRef[root] = 1
+ s.node = append(s.node, nil, &serveNode{
+ inode: 1,
+ generation: s.nodeGen,
+ node: root,
+ refs: 1,
+ })
+ s.handle = append(s.handle, nil)
+
+ for {
+ req, err := s.conn.ReadRequest()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return err
+ }
+
+ s.wg.Add(1)
+ go func() {
+ defer s.wg.Done()
+ s.serve(req)
+ }()
+ }
+ return nil
+}
+
+// Serve serves a FUSE connection with the default settings. See
+// Server.Serve.
+func Serve(c *fuse.Conn, fs FS) error {
+ server := New(c, nil)
+ return server.Serve(fs)
+}
+
+type nothing struct{}
+
+type serveRequest struct {
+ Request fuse.Request
+ cancel func()
+}
+
+type serveNode struct {
+ inode uint64
+ generation uint64
+ node Node
+ refs uint64
+
+ // Delay freeing the NodeID until waitgroup is done. This allows
+ // using the NodeID for short periods of time without holding the
+ // Server.meta lock.
+ //
+ // Rules:
+ //
+ // - hold Server.meta while calling wg.Add, then unlock
+ // - do NOT try to reacquire Server.meta
+ wg sync.WaitGroup
+}
+
+func (sn *serveNode) attr(ctx context.Context, attr *fuse.Attr) error {
+ err := nodeAttr(ctx, sn.node, attr)
+ if attr.Inode == 0 {
+ attr.Inode = sn.inode
+ }
+ return err
+}
+
+type serveHandle struct {
+ handle Handle
+ readData []byte
+ nodeID fuse.NodeID
+}
+
+// NodeRef is deprecated. It remains here to decrease code churn on
+// FUSE library users. You may remove it from your program now;
+// returning the same Node values are now recognized automatically,
+// without needing NodeRef.
+type NodeRef struct{}
+
+func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) {
+ c.meta.Lock()
+ defer c.meta.Unlock()
+
+ if id, ok := c.nodeRef[node]; ok {
+ sn := c.node[id]
+ sn.refs++
+ return id, sn.generation
+ }
+
+ sn := &serveNode{inode: inode, node: node, refs: 1}
+ if n := len(c.freeNode); n > 0 {
+ id = c.freeNode[n-1]
+ c.freeNode = c.freeNode[:n-1]
+ c.node[id] = sn
+ c.nodeGen++
+ } else {
+ id = fuse.NodeID(len(c.node))
+ c.node = append(c.node, sn)
+ }
+ sn.generation = c.nodeGen
+ c.nodeRef[node] = id
+ return id, sn.generation
+}
+
+func (c *Server) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) {
+ c.meta.Lock()
+ shandle := &serveHandle{handle: handle, nodeID: nodeID}
+ if n := len(c.freeHandle); n > 0 {
+ id = c.freeHandle[n-1]
+ c.freeHandle = c.freeHandle[:n-1]
+ c.handle[id] = shandle
+ } else {
+ id = fuse.HandleID(len(c.handle))
+ c.handle = append(c.handle, shandle)
+ }
+ c.meta.Unlock()
+ return
+}
+
+type nodeRefcountDropBug struct {
+ N uint64
+ Refs uint64
+ Node fuse.NodeID
+}
+
+func (n *nodeRefcountDropBug) String() string {
+ return fmt.Sprintf("bug: trying to drop %d of %d references to %v", n.N, n.Refs, n.Node)
+}
+
+func (c *Server) dropNode(id fuse.NodeID, n uint64) (forget bool) {
+ c.meta.Lock()
+ defer c.meta.Unlock()
+ snode := c.node[id]
+
+ if snode == nil {
+ // this should only happen if refcounts kernel<->us disagree
+ // *and* two ForgetRequests for the same node race each other;
+ // this indicates a bug somewhere
+ c.debug(nodeRefcountDropBug{N: n, Node: id})
+
+ // we may end up triggering Forget twice, but that's better
+ // than not even once, and that's the best we can do
+ return true
+ }
+
+ if n > snode.refs {
+ c.debug(nodeRefcountDropBug{N: n, Refs: snode.refs, Node: id})
+ n = snode.refs
+ }
+
+ snode.refs -= n
+ if snode.refs == 0 {
+ snode.wg.Wait()
+ c.node[id] = nil
+ delete(c.nodeRef, snode.node)
+ c.freeNode = append(c.freeNode, id)
+ return true
+ }
+ return false
+}
+
+func (c *Server) dropHandle(id fuse.HandleID) {
+ c.meta.Lock()
+ c.handle[id] = nil
+ c.freeHandle = append(c.freeHandle, id)
+ c.meta.Unlock()
+}
+
+type missingHandle struct {
+ Handle fuse.HandleID
+ MaxHandle fuse.HandleID
+}
+
+func (m missingHandle) String() string {
+ return fmt.Sprint("missing handle: ", m.Handle, m.MaxHandle)
+}
+
+// Returns nil for invalid handles.
+func (c *Server) getHandle(id fuse.HandleID) (shandle *serveHandle) {
+ c.meta.Lock()
+ defer c.meta.Unlock()
+ if id < fuse.HandleID(len(c.handle)) {
+ shandle = c.handle[uint(id)]
+ }
+ if shandle == nil {
+ c.debug(missingHandle{
+ Handle: id,
+ MaxHandle: fuse.HandleID(len(c.handle)),
+ })
+ }
+ return
+}
+
+type request struct {
+ Op string
+ Request *fuse.Header
+ In interface{} `json:",omitempty"`
+}
+
+func (r request) String() string {
+ return fmt.Sprintf("<- %s", r.In)
+}
+
+type logResponseHeader struct {
+ ID fuse.RequestID
+}
+
+func (m logResponseHeader) String() string {
+ return fmt.Sprintf("ID=%v", m.ID)
+}
+
+type response struct {
+ Op string
+ Request logResponseHeader
+ Out interface{} `json:",omitempty"`
+ // Errno contains the errno value as a string, for example "EPERM".
+ Errno string `json:",omitempty"`
+ // Error may contain a free form error message.
+ Error string `json:",omitempty"`
+}
+
+func (r response) errstr() string {
+ s := r.Errno
+ if r.Error != "" {
+ // prefix the errno constant to the long form message
+ s = s + ": " + r.Error
+ }
+ return s
+}
+
+func (r response) String() string {
+ switch {
+ case r.Errno != "" && r.Out != nil:
+ return fmt.Sprintf("-> [%v] %v error=%s", r.Request, r.Out, r.errstr())
+ case r.Errno != "":
+ return fmt.Sprintf("-> [%v] %s error=%s", r.Request, r.Op, r.errstr())
+ case r.Out != nil:
+ // make sure (seemingly) empty values are readable
+ switch r.Out.(type) {
+ case string:
+ return fmt.Sprintf("-> [%v] %s %q", r.Request, r.Op, r.Out)
+ case []byte:
+ return fmt.Sprintf("-> [%v] %s [% x]", r.Request, r.Op, r.Out)
+ default:
+ return fmt.Sprintf("-> [%v] %v", r.Request, r.Out)
+ }
+ default:
+ return fmt.Sprintf("-> [%v] %s", r.Request, r.Op)
+ }
+}
+
+type notification struct {
+ Op string
+ Node fuse.NodeID
+ Out interface{} `json:",omitempty"`
+ Err string `json:",omitempty"`
+}
+
+func (n notification) String() string {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "=> %s %v", n.Op, n.Node)
+ if n.Out != nil {
+ // make sure (seemingly) empty values are readable
+ switch n.Out.(type) {
+ case string:
+ fmt.Fprintf(&buf, " %q", n.Out)
+ case []byte:
+ fmt.Fprintf(&buf, " [% x]", n.Out)
+ default:
+ fmt.Fprintf(&buf, " %s", n.Out)
+ }
+ }
+ if n.Err != "" {
+ fmt.Fprintf(&buf, " Err:%v", n.Err)
+ }
+ return buf.String()
+}
+
+type logMissingNode struct {
+ MaxNode fuse.NodeID
+}
+
+func opName(req fuse.Request) string {
+ t := reflect.Indirect(reflect.ValueOf(req)).Type()
+ s := t.Name()
+ s = strings.TrimSuffix(s, "Request")
+ return s
+}
+
+type logLinkRequestOldNodeNotFound struct {
+ Request *fuse.Header
+ In *fuse.LinkRequest
+}
+
+func (m *logLinkRequestOldNodeNotFound) String() string {
+ return fmt.Sprintf("In LinkRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.OldNode)
+}
+
+type renameNewDirNodeNotFound struct {
+ Request *fuse.Header
+ In *fuse.RenameRequest
+}
+
+func (m *renameNewDirNodeNotFound) String() string {
+ return fmt.Sprintf("In RenameRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.NewDir)
+}
+
+type handlerPanickedError struct {
+ Request interface{}
+ Err interface{}
+}
+
+var _ error = handlerPanickedError{}
+
+func (h handlerPanickedError) Error() string {
+ return fmt.Sprintf("handler panicked: %v", h.Err)
+}
+
+var _ fuse.ErrorNumber = handlerPanickedError{}
+
+func (h handlerPanickedError) Errno() fuse.Errno {
+ if err, ok := h.Err.(fuse.ErrorNumber); ok {
+ return err.Errno()
+ }
+ return fuse.DefaultErrno
+}
+
+// handlerTerminatedError happens when a handler terminates itself
+// with runtime.Goexit. This is most commonly because of incorrect use
+// of testing.TB.FailNow, typically via t.Fatal.
+type handlerTerminatedError struct {
+ Request interface{}
+}
+
+var _ error = handlerTerminatedError{}
+
+func (h handlerTerminatedError) Error() string {
+ return fmt.Sprintf("handler terminated (called runtime.Goexit)")
+}
+
+var _ fuse.ErrorNumber = handlerTerminatedError{}
+
+func (h handlerTerminatedError) Errno() fuse.Errno {
+ return fuse.DefaultErrno
+}
+
+type handleNotReaderError struct {
+ handle Handle
+}
+
+var _ error = handleNotReaderError{}
+
+func (e handleNotReaderError) Error() string {
+ return fmt.Sprintf("handle has no Read: %T", e.handle)
+}
+
+var _ fuse.ErrorNumber = handleNotReaderError{}
+
+func (e handleNotReaderError) Errno() fuse.Errno {
+ return fuse.ENOTSUP
+}
+
+func initLookupResponse(s *fuse.LookupResponse) {
+ s.EntryValid = entryValidTime
+}
+
+func (c *Server) serve(r fuse.Request) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ parentCtx := ctx
+ if c.context != nil {
+ ctx = c.context(ctx, r)
+ }
+
+ req := &serveRequest{Request: r, cancel: cancel}
+
+ c.debug(request{
+ Op: opName(r),
+ Request: r.Hdr(),
+ In: r,
+ })
+ var node Node
+ var snode *serveNode
+ c.meta.Lock()
+ hdr := r.Hdr()
+ if id := hdr.Node; id != 0 {
+ if id < fuse.NodeID(len(c.node)) {
+ snode = c.node[uint(id)]
+ }
+ if snode == nil {
+ c.meta.Unlock()
+ c.debug(response{
+ Op: opName(r),
+ Request: logResponseHeader{ID: hdr.ID},
+ Error: fuse.ESTALE.ErrnoName(),
+ // this is the only place that sets both Error and
+ // Out; not sure if i want to do that; might get rid
+ // of len(c.node) things altogether
+ Out: logMissingNode{
+ MaxNode: fuse.NodeID(len(c.node)),
+ },
+ })
+ r.RespondError(fuse.ESTALE)
+ return
+ }
+ node = snode.node
+ }
+ if c.req[hdr.ID] != nil {
+ // This happens with OSXFUSE. Assume it's okay and
+ // that we'll never see an interrupt for this one.
+ // Otherwise everything wedges. TODO: Report to OSXFUSE?
+ //
+ // TODO this might have been because of missing done() calls
+ } else {
+ c.req[hdr.ID] = req
+ }
+ c.meta.Unlock()
+
+ // Call this before responding.
+ // After responding is too late: we might get another request
+ // with the same ID and be very confused.
+ done := func(resp interface{}) {
+ msg := response{
+ Op: opName(r),
+ Request: logResponseHeader{ID: hdr.ID},
+ }
+ if err, ok := resp.(error); ok {
+ msg.Error = err.Error()
+ if ferr, ok := err.(fuse.ErrorNumber); ok {
+ errno := ferr.Errno()
+ msg.Errno = errno.ErrnoName()
+ if errno == err {
+ // it's just a fuse.Errno with no extra detail;
+ // skip the textual message for log readability
+ msg.Error = ""
+ }
+ } else {
+ msg.Errno = fuse.DefaultErrno.ErrnoName()
+ }
+ } else {
+ msg.Out = resp
+ }
+ c.debug(msg)
+
+ c.meta.Lock()
+ delete(c.req, hdr.ID)
+ c.meta.Unlock()
+ }
+
+ var responded bool
+ defer func() {
+ if rec := recover(); rec != nil {
+ const size = 1 << 16
+ buf := make([]byte, size)
+ n := runtime.Stack(buf, false)
+ buf = buf[:n]
+ log.Printf("fuse: panic in handler for %v: %v\n%s", r, rec, buf)
+ err := handlerPanickedError{
+ Request: r,
+ Err: rec,
+ }
+ done(err)
+ r.RespondError(err)
+ return
+ }
+
+ if !responded {
+ err := handlerTerminatedError{
+ Request: r,
+ }
+ done(err)
+ r.RespondError(err)
+ }
+ }()
+
+ if err := c.handleRequest(ctx, node, snode, r, done); err != nil {
+ if err == context.Canceled {
+ select {
+ case <-parentCtx.Done():
+ // We canceled the parent context because of an
+ // incoming interrupt request, so return EINTR
+ // to trigger the right behavior in the client app.
+ //
+ // Only do this when it's the parent context that was
+ // canceled, not a context controlled by the program
+ // using this library, so we don't return EINTR too
+ // eagerly -- it might cause busy loops.
+ //
+ // Decent write-up on role of EINTR:
+ // http://250bpm.com/blog:12
+ err = fuse.EINTR
+ default:
+ // nothing
+ }
+ }
+ done(err)
+ r.RespondError(err)
+ }
+
+ // disarm runtime.Goexit protection
+ responded = true
+}
+
+// handleRequest will either a) call done(s) and r.Respond(s) OR b) return an error.
+func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode, r fuse.Request, done func(resp interface{})) error {
+ switch r := r.(type) {
+ default:
+ // Note: To FUSE, ENOSYS means "this server never implements this request."
+ // It would be inappropriate to return ENOSYS for other operations in this
+ // switch that might only be unavailable in some contexts, not all.
+ return fuse.ENOSYS
+
+ case *fuse.StatfsRequest:
+ s := &fuse.StatfsResponse{}
+ if fs, ok := c.fs.(FSStatfser); ok {
+ if err := fs.Statfs(ctx, r, s); err != nil {
+ return err
+ }
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ // Node operations.
+ case *fuse.GetattrRequest:
+ s := &fuse.GetattrResponse{}
+ if n, ok := node.(NodeGetattrer); ok {
+ if err := n.Getattr(ctx, r, s); err != nil {
+ return err
+ }
+ } else {
+ if err := snode.attr(ctx, &s.Attr); err != nil {
+ return err
+ }
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.SetattrRequest:
+ s := &fuse.SetattrResponse{}
+ if n, ok := node.(NodeSetattrer); ok {
+ if err := n.Setattr(ctx, r, s); err != nil {
+ return err
+ }
+ }
+
+ if err := snode.attr(ctx, &s.Attr); err != nil {
+ return err
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.SymlinkRequest:
+ s := &fuse.SymlinkResponse{}
+ initLookupResponse(&s.LookupResponse)
+ n, ok := node.(NodeSymlinker)
+ if !ok {
+ return fuse.EIO // XXX or EPERM like Mkdir?
+ }
+ n2, err := n.Symlink(ctx, r)
+ if err != nil {
+ return err
+ }
+ if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil {
+ return err
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.ReadlinkRequest:
+ n, ok := node.(NodeReadlinker)
+ if !ok {
+ return fuse.EIO /// XXX or EPERM?
+ }
+ target, err := n.Readlink(ctx, r)
+ if err != nil {
+ return err
+ }
+ done(target)
+ r.Respond(target)
+ return nil
+
+ case *fuse.LinkRequest:
+ n, ok := node.(NodeLinker)
+ if !ok {
+ return fuse.EIO /// XXX or EPERM?
+ }
+ c.meta.Lock()
+ var oldNode *serveNode
+ if int(r.OldNode) < len(c.node) {
+ oldNode = c.node[r.OldNode]
+ }
+ c.meta.Unlock()
+ if oldNode == nil {
+ c.debug(logLinkRequestOldNodeNotFound{
+ Request: r.Hdr(),
+ In: r,
+ })
+ return fuse.EIO
+ }
+ n2, err := n.Link(ctx, r, oldNode.node)
+ if err != nil {
+ return err
+ }
+ s := &fuse.LookupResponse{}
+ initLookupResponse(s)
+ if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil {
+ return err
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.RemoveRequest:
+ n, ok := node.(NodeRemover)
+ if !ok {
+ return fuse.EIO /// XXX or EPERM?
+ }
+ err := n.Remove(ctx, r)
+ if err != nil {
+ return err
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.AccessRequest:
+ if n, ok := node.(NodeAccesser); ok {
+ if err := n.Access(ctx, r); err != nil {
+ return err
+ }
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.LookupRequest:
+ var n2 Node
+ var err error
+ s := &fuse.LookupResponse{}
+ initLookupResponse(s)
+ if n, ok := node.(NodeStringLookuper); ok {
+ n2, err = n.Lookup(ctx, r.Name)
+ } else if n, ok := node.(NodeRequestLookuper); ok {
+ n2, err = n.Lookup(ctx, r, s)
+ } else {
+ return fuse.ENOENT
+ }
+ if err != nil {
+ return err
+ }
+ if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
+ return err
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.MkdirRequest:
+ s := &fuse.MkdirResponse{}
+ initLookupResponse(&s.LookupResponse)
+ n, ok := node.(NodeMkdirer)
+ if !ok {
+ return fuse.EPERM
+ }
+ n2, err := n.Mkdir(ctx, r)
+ if err != nil {
+ return err
+ }
+ if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
+ return err
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.OpenRequest:
+ s := &fuse.OpenResponse{}
+ var h2 Handle
+ if n, ok := node.(NodeOpener); ok {
+ hh, err := n.Open(ctx, r, s)
+ if err != nil {
+ return err
+ }
+ h2 = hh
+ } else {
+ h2 = node
+ }
+ s.Handle = c.saveHandle(h2, r.Hdr().Node)
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.CreateRequest:
+ n, ok := node.(NodeCreater)
+ if !ok {
+ // If we send back ENOSYS, FUSE will try mknod+open.
+ return fuse.EPERM
+ }
+ s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}}
+ initLookupResponse(&s.LookupResponse)
+ n2, h2, err := n.Create(ctx, r, s)
+ if err != nil {
+ return err
+ }
+ if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
+ return err
+ }
+ s.Handle = c.saveHandle(h2, r.Hdr().Node)
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.GetxattrRequest:
+ n, ok := node.(NodeGetxattrer)
+ if !ok {
+ return fuse.ENOTSUP
+ }
+ s := &fuse.GetxattrResponse{}
+ err := n.Getxattr(ctx, r, s)
+ if err != nil {
+ return err
+ }
+ if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
+ return fuse.ERANGE
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.ListxattrRequest:
+ n, ok := node.(NodeListxattrer)
+ if !ok {
+ return fuse.ENOTSUP
+ }
+ s := &fuse.ListxattrResponse{}
+ err := n.Listxattr(ctx, r, s)
+ if err != nil {
+ return err
+ }
+ if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
+ return fuse.ERANGE
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.SetxattrRequest:
+ n, ok := node.(NodeSetxattrer)
+ if !ok {
+ return fuse.ENOTSUP
+ }
+ err := n.Setxattr(ctx, r)
+ if err != nil {
+ return err
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.RemovexattrRequest:
+ n, ok := node.(NodeRemovexattrer)
+ if !ok {
+ return fuse.ENOTSUP
+ }
+ err := n.Removexattr(ctx, r)
+ if err != nil {
+ return err
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.ForgetRequest:
+ forget := c.dropNode(r.Hdr().Node, r.N)
+ if forget {
+ n, ok := node.(NodeForgetter)
+ if ok {
+ n.Forget()
+ }
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ // Handle operations.
+ case *fuse.ReadRequest:
+ shandle := c.getHandle(r.Handle)
+ if shandle == nil {
+ return fuse.ESTALE
+ }
+ handle := shandle.handle
+
+ s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)}
+ if r.Dir {
+ if h, ok := handle.(HandleReadDirAller); ok {
+ // detect rewinddir(3) or similar seek and refresh
+ // contents
+ if r.Offset == 0 {
+ shandle.readData = nil
+ }
+
+ if shandle.readData == nil {
+ dirs, err := h.ReadDirAll(ctx)
+ if err != nil {
+ return err
+ }
+ var data []byte
+ for _, dir := range dirs {
+ if dir.Inode == 0 {
+ dir.Inode = c.dynamicInode(snode.inode, dir.Name)
+ }
+ data = fuse.AppendDirent(data, dir)
+ }
+ shandle.readData = data
+ }
+ fuseutil.HandleRead(r, s, shandle.readData)
+ done(s)
+ r.Respond(s)
+ return nil
+ }
+ } else {
+ if h, ok := handle.(HandleReadAller); ok {
+ if shandle.readData == nil {
+ data, err := h.ReadAll(ctx)
+ if err != nil {
+ return err
+ }
+ if data == nil {
+ data = []byte{}
+ }
+ shandle.readData = data
+ }
+ fuseutil.HandleRead(r, s, shandle.readData)
+ done(s)
+ r.Respond(s)
+ return nil
+ }
+ h, ok := handle.(HandleReader)
+ if !ok {
+ err := handleNotReaderError{handle: handle}
+ return err
+ }
+ if err := h.Read(ctx, r, s); err != nil {
+ return err
+ }
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.WriteRequest:
+ shandle := c.getHandle(r.Handle)
+ if shandle == nil {
+ return fuse.ESTALE
+ }
+
+ s := &fuse.WriteResponse{}
+ if h, ok := shandle.handle.(HandleWriter); ok {
+ if err := h.Write(ctx, r, s); err != nil {
+ return err
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+ }
+ return fuse.EIO
+
+ case *fuse.FlushRequest:
+ shandle := c.getHandle(r.Handle)
+ if shandle == nil {
+ return fuse.ESTALE
+ }
+ handle := shandle.handle
+
+ if h, ok := handle.(HandleFlusher); ok {
+ if err := h.Flush(ctx, r); err != nil {
+ return err
+ }
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.ReleaseRequest:
+ shandle := c.getHandle(r.Handle)
+ if shandle == nil {
+ return fuse.ESTALE
+ }
+ handle := shandle.handle
+
+ // No matter what, release the handle.
+ c.dropHandle(r.Handle)
+
+ if h, ok := handle.(HandleReleaser); ok {
+ if err := h.Release(ctx, r); err != nil {
+ return err
+ }
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.DestroyRequest:
+ if fs, ok := c.fs.(FSDestroyer); ok {
+ fs.Destroy()
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.RenameRequest:
+ c.meta.Lock()
+ var newDirNode *serveNode
+ if int(r.NewDir) < len(c.node) {
+ newDirNode = c.node[r.NewDir]
+ }
+ c.meta.Unlock()
+ if newDirNode == nil {
+ c.debug(renameNewDirNodeNotFound{
+ Request: r.Hdr(),
+ In: r,
+ })
+ return fuse.EIO
+ }
+ n, ok := node.(NodeRenamer)
+ if !ok {
+ return fuse.EIO // XXX or EPERM like Mkdir?
+ }
+ err := n.Rename(ctx, r, newDirNode.node)
+ if err != nil {
+ return err
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.MknodRequest:
+ n, ok := node.(NodeMknoder)
+ if !ok {
+ return fuse.EIO
+ }
+ n2, err := n.Mknod(ctx, r)
+ if err != nil {
+ return err
+ }
+ s := &fuse.LookupResponse{}
+ initLookupResponse(s)
+ if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
+ return err
+ }
+ done(s)
+ r.Respond(s)
+ return nil
+
+ case *fuse.FsyncRequest:
+ n, ok := node.(NodeFsyncer)
+ if !ok {
+ return fuse.EIO
+ }
+ err := n.Fsync(ctx, r)
+ if err != nil {
+ return err
+ }
+ done(nil)
+ r.Respond()
+ return nil
+
+ case *fuse.InterruptRequest:
+ c.meta.Lock()
+ ireq := c.req[r.IntrID]
+ if ireq != nil && ireq.cancel != nil {
+ ireq.cancel()
+ ireq.cancel = nil
+ }
+ c.meta.Unlock()
+ done(nil)
+ r.Respond()
+ return nil
+
+ /* case *FsyncdirRequest:
+ return ENOSYS
+
+ case *GetlkRequest, *SetlkRequest, *SetlkwRequest:
+ return ENOSYS
+
+ case *BmapRequest:
+ return ENOSYS
+
+ case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest:
+ return ENOSYS
+ */
+ }
+
+ panic("not reached")
+}
+
+func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error {
+ if err := nodeAttr(ctx, n2, &s.Attr); err != nil {
+ return err
+ }
+ if s.Attr.Inode == 0 {
+ s.Attr.Inode = c.dynamicInode(snode.inode, elem)
+ }
+
+ s.Node, s.Generation = c.saveNode(s.Attr.Inode, n2)
+ return nil
+}
+
+type invalidateNodeDetail struct {
+ Off int64
+ Size int64
+}
+
+func (i invalidateNodeDetail) String() string {
+ return fmt.Sprintf("Off:%d Size:%d", i.Off, i.Size)
+}
+
+func errstr(err error) string {
+ if err == nil {
+ return ""
+ }
+ return err.Error()
+}
+
+func (s *Server) invalidateNode(node Node, off int64, size int64) error {
+ s.meta.Lock()
+ id, ok := s.nodeRef[node]
+ if ok {
+ snode := s.node[id]
+ snode.wg.Add(1)
+ defer snode.wg.Done()
+ }
+ s.meta.Unlock()
+ if !ok {
+ // This is what the kernel would have said, if we had been
+ // able to send this message; it's not cached.
+ return fuse.ErrNotCached
+ }
+ // Delay logging until after we can record the error too. We
+ // consider a /dev/fuse write to be instantaneous enough to not
+ // need separate before and after messages.
+ err := s.conn.InvalidateNode(id, off, size)
+ s.debug(notification{
+ Op: "InvalidateNode",
+ Node: id,
+ Out: invalidateNodeDetail{
+ Off: off,
+ Size: size,
+ },
+ Err: errstr(err),
+ })
+ return err
+}
+
+// InvalidateNodeAttr invalidates the kernel cache of the attributes
+// of node.
+//
+// Returns fuse.ErrNotCached if the kernel is not currently caching
+// the node.
+func (s *Server) InvalidateNodeAttr(node Node) error {
+ return s.invalidateNode(node, 0, 0)
+}
+
+// InvalidateNodeData invalidates the kernel cache of the attributes
+// and data of node.
+//
+// Returns fuse.ErrNotCached if the kernel is not currently caching
+// the node.
+func (s *Server) InvalidateNodeData(node Node) error {
+ return s.invalidateNode(node, 0, -1)
+}
+
+// InvalidateNodeDataRange invalidates the kernel cache of the
+// attributes and a range of the data of node.
+//
+// Returns fuse.ErrNotCached if the kernel is not currently caching
+// the node.
+func (s *Server) InvalidateNodeDataRange(node Node, off int64, size int64) error {
+ return s.invalidateNode(node, off, size)
+}
+
+type invalidateEntryDetail struct {
+ Name string
+}
+
+func (i invalidateEntryDetail) String() string {
+ return fmt.Sprintf("%q", i.Name)
+}
+
+// InvalidateEntry invalidates the kernel cache of the directory entry
+// identified by parent node and entry basename.
+//
+// Kernel may or may not cache directory listings. To invalidate
+// those, use InvalidateNode to invalidate all of the data for a
+// directory. (As of 2015-06, Linux FUSE does not cache directory
+// listings.)
+//
+// Returns ErrNotCached if the kernel is not currently caching the
+// node.
+func (s *Server) InvalidateEntry(parent Node, name string) error {
+ s.meta.Lock()
+ id, ok := s.nodeRef[parent]
+ if ok {
+ snode := s.node[id]
+ snode.wg.Add(1)
+ defer snode.wg.Done()
+ }
+ s.meta.Unlock()
+ if !ok {
+ // This is what the kernel would have said, if we had been
+ // able to send this message; it's not cached.
+ return fuse.ErrNotCached
+ }
+ err := s.conn.InvalidateEntry(id, name)
+ s.debug(notification{
+ Op: "InvalidateEntry",
+ Node: id,
+ Out: invalidateEntryDetail{
+ Name: name,
+ },
+ Err: errstr(err),
+ })
+ return err
+}
+
+// DataHandle returns a read-only Handle that satisfies reads
+// using the given data.
+func DataHandle(data []byte) Handle {
+ return &dataHandle{data}
+}
+
+type dataHandle struct {
+ data []byte
+}
+
+func (d *dataHandle) ReadAll(ctx context.Context) ([]byte, error) {
+ return d.data, nil
+}
+
+// GenerateDynamicInode returns a dynamic inode.
+//
+// The parent inode and current entry name are used as the criteria
+// for choosing a pseudorandom inode. This makes it likely the same
+// entry will get the same inode on multiple runs.
+func GenerateDynamicInode(parent uint64, name string) uint64 {
+ h := fnv.New64a()
+ var buf [8]byte
+ binary.LittleEndian.PutUint64(buf[:], parent)
+ _, _ = h.Write(buf[:])
+ _, _ = h.Write([]byte(name))
+ var inode uint64
+ for {
+ inode = h.Sum64()
+ if inode != 0 {
+ break
+ }
+ // there's a tiny probability that result is zero; change the
+ // input a little and try again
+ _, _ = h.Write([]byte{'x'})
+ }
+ return inode
+}
diff --git a/vendor/bazil.org/fuse/fs/tree.go b/vendor/bazil.org/fuse/fs/tree.go
new file mode 100644
index 000000000..7e078045a
--- /dev/null
+++ b/vendor/bazil.org/fuse/fs/tree.go
@@ -0,0 +1,99 @@
+// FUSE directory tree, for servers that wish to use it with the service loop.
+
+package fs
+
+import (
+ "os"
+ pathpkg "path"
+ "strings"
+
+ "golang.org/x/net/context"
+)
+
+import (
+ "bazil.org/fuse"
+)
+
+// A Tree implements a basic read-only directory tree for FUSE.
+// The Nodes contained in it may still be writable.
+type Tree struct {
+ tree
+}
+
+func (t *Tree) Root() (Node, error) {
+ return &t.tree, nil
+}
+
+// Add adds the path to the tree, resolving to the given node.
+// If path or a prefix of path has already been added to the tree,
+// Add panics.
+//
+// Add is only safe to call before starting to serve requests.
+func (t *Tree) Add(path string, node Node) {
+ path = pathpkg.Clean("/" + path)[1:]
+ elems := strings.Split(path, "/")
+ dir := Node(&t.tree)
+ for i, elem := range elems {
+ dt, ok := dir.(*tree)
+ if !ok {
+ panic("fuse: Tree.Add for " + strings.Join(elems[:i], "/") + " and " + path)
+ }
+ n := dt.lookup(elem)
+ if n != nil {
+ if i+1 == len(elems) {
+ panic("fuse: Tree.Add for " + path + " conflicts with " + elem)
+ }
+ dir = n
+ } else {
+ if i+1 == len(elems) {
+ dt.add(elem, node)
+ } else {
+ dir = &tree{}
+ dt.add(elem, dir)
+ }
+ }
+ }
+}
+
+type treeDir struct {
+ name string
+ node Node
+}
+
+type tree struct {
+ dir []treeDir
+}
+
+func (t *tree) lookup(name string) Node {
+ for _, d := range t.dir {
+ if d.name == name {
+ return d.node
+ }
+ }
+ return nil
+}
+
+func (t *tree) add(name string, n Node) {
+ t.dir = append(t.dir, treeDir{name, n})
+}
+
+func (t *tree) Attr(ctx context.Context, a *fuse.Attr) error {
+ a.Mode = os.ModeDir | 0555
+ return nil
+}
+
+func (t *tree) Lookup(ctx context.Context, name string) (Node, error) {
+ n := t.lookup(name)
+ if n != nil {
+ return n, nil
+ }
+ return nil, fuse.ENOENT
+}
+
+func (t *tree) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
+ var out []fuse.Dirent
+ for _, d := range t.dir {
+ out = append(out, fuse.Dirent{Name: d.name})
+ }
+ return out, nil
+}
diff --git a/vendor/bazil.org/fuse/fuse.go b/vendor/bazil.org/fuse/fuse.go
new file mode 100644
index 000000000..6db0ef293
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse.go
@@ -0,0 +1,2303 @@
+// See the file LICENSE for copyright and licensing information.
+
+// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
+// which carries this notice:
+//
+// The files in this directory are subject to the following license.
+//
+// The author of this software is Russ Cox.
+//
+// Copyright (c) 2006 Russ Cox
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose without fee is hereby granted, provided that this entire notice
+// is included in all copies of any software which is or includes a copy
+// or modification of this software and in all copies of the supporting
+// documentation for such software.
+//
+// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
+// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
+// FITNESS FOR ANY PARTICULAR PURPOSE.
+
+// Package fuse enables writing FUSE file systems on Linux, OS X, and FreeBSD.
+//
+// On OS X, it requires OSXFUSE (http://osxfuse.github.com/).
+//
+// There are two approaches to writing a FUSE file system. The first is to speak
+// the low-level message protocol, reading from a Conn using ReadRequest and
+// writing using the various Respond methods. This approach is closest to
+// the actual interaction with the kernel and can be the simplest one in contexts
+// such as protocol translators.
+//
+// Servers of synthesized file systems tend to share common
+// bookkeeping abstracted away by the second approach, which is to
+// call fs.Serve to serve the FUSE protocol using an implementation of
+// the service methods in the interfaces FS* (file system), Node* (file
+// or directory), and Handle* (opened file or directory).
+// There are a daunting number of such methods that can be written,
+// but few are required.
+// The specific methods are described in the documentation for those interfaces.
+//
+// The hellofs subdirectory contains a simple illustration of the fs.Serve approach.
+//
+// Service Methods
+//
+// The required and optional methods for the FS, Node, and Handle interfaces
+// have the general form
+//
+// Op(ctx context.Context, req *OpRequest, resp *OpResponse) error
+//
+// where Op is the name of a FUSE operation. Op reads request
+// parameters from req and writes results to resp. An operation whose
+// only result is the error result omits the resp parameter.
+//
+// Multiple goroutines may call service methods simultaneously; the
+// methods being called are responsible for appropriate
+// synchronization.
+//
+// The operation must not hold on to the request or response,
+// including any []byte fields such as WriteRequest.Data or
+// SetxattrRequest.Xattr.
+//
+// Errors
+//
+// Operations can return errors. The FUSE interface can only
+// communicate POSIX errno error numbers to file system clients, the
+// message is not visible to file system clients. The returned error
+// can implement ErrorNumber to control the errno returned. Without
+// ErrorNumber, a generic errno (EIO) is returned.
+//
+// Error messages will be visible in the debug log as part of the
+// response.
+//
+// Interrupted Operations
+//
+// In some file systems, some operations
+// may take an undetermined amount of time. For example, a Read waiting for
+// a network message or a matching Write might wait indefinitely. If the request
+// is cancelled and no longer needed, the context will be cancelled.
+// Blocking operations should select on a receive from ctx.Done() and attempt to
+// abort the operation early if the receive succeeds (meaning the channel is closed).
+// To indicate that the operation failed because it was aborted, return fuse.EINTR.
+//
+// If an operation does not block for an indefinite amount of time, supporting
+// cancellation is not necessary.
+//
+// Authentication
+//
+// All requests types embed a Header, meaning that the method can
+// inspect req.Pid, req.Uid, and req.Gid as necessary to implement
+// permission checking. The kernel FUSE layer normally prevents other
+// users from accessing the FUSE file system (to change this, see
+// AllowOther, AllowRoot), but does not enforce access modes (to
+// change this, see DefaultPermissions).
+//
+// Mount Options
+//
+// Behavior and metadata of the mounted file system can be changed by
+// passing MountOption values to Mount.
+//
+package fuse // import "bazil.org/fuse"
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "sync"
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+// A Conn represents a connection to a mounted FUSE file system.
+type Conn struct {
+ // Ready is closed when the mount is complete or has failed.
+ Ready <-chan struct{}
+
+ // MountError stores any error from the mount process. Only valid
+ // after Ready is closed.
+ MountError error
+
+ // File handle for kernel communication. Only safe to access if
+ // rio or wio is held.
+ dev *os.File
+ wio sync.RWMutex
+ rio sync.RWMutex
+
+ // Protocol version negotiated with InitRequest/InitResponse.
+ proto Protocol
+}
+
+// MountpointDoesNotExistError is an error returned when the
+// mountpoint does not exist.
+type MountpointDoesNotExistError struct {
+ Path string
+}
+
+var _ error = (*MountpointDoesNotExistError)(nil)
+
+func (e *MountpointDoesNotExistError) Error() string {
+ return fmt.Sprintf("mountpoint does not exist: %v", e.Path)
+}
+
+// Mount mounts a new FUSE connection on the named directory
+// and returns a connection for reading and writing FUSE messages.
+//
+// After a successful return, caller must call Close to free
+// resources.
+//
+// Even on successful return, the new mount is not guaranteed to be
+// visible until after Conn.Ready is closed. See Conn.MountError for
+// possible errors. Incoming requests on Conn must be served to make
+// progress.
+func Mount(dir string, options ...MountOption) (*Conn, error) {
+ conf := mountConfig{
+ options: make(map[string]string),
+ }
+ for _, option := range options {
+ if err := option(&conf); err != nil {
+ return nil, err
+ }
+ }
+
+ ready := make(chan struct{}, 1)
+ c := &Conn{
+ Ready: ready,
+ }
+ f, err := mount(dir, &conf, ready, &c.MountError)
+ if err != nil {
+ return nil, err
+ }
+ c.dev = f
+
+ if err := initMount(c, &conf); err != nil {
+ c.Close()
+ if err == ErrClosedWithoutInit {
+ // see if we can provide a better error
+ <-c.Ready
+ if err := c.MountError; err != nil {
+ return nil, err
+ }
+ }
+ return nil, err
+ }
+
+ return c, nil
+}
+
+type OldVersionError struct {
+ Kernel Protocol
+ LibraryMin Protocol
+}
+
+func (e *OldVersionError) Error() string {
+ return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin)
+}
+
+var (
+ ErrClosedWithoutInit = errors.New("fuse connection closed without init")
+)
+
+func initMount(c *Conn, conf *mountConfig) error {
+ req, err := c.ReadRequest()
+ if err != nil {
+ if err == io.EOF {
+ return ErrClosedWithoutInit
+ }
+ return err
+ }
+ r, ok := req.(*InitRequest)
+ if !ok {
+ return fmt.Errorf("missing init, got: %T", req)
+ }
+
+ min := Protocol{protoVersionMinMajor, protoVersionMinMinor}
+ if r.Kernel.LT(min) {
+ req.RespondError(Errno(syscall.EPROTO))
+ c.Close()
+ return &OldVersionError{
+ Kernel: r.Kernel,
+ LibraryMin: min,
+ }
+ }
+
+ proto := Protocol{protoVersionMaxMajor, protoVersionMaxMinor}
+ if r.Kernel.LT(proto) {
+ // Kernel doesn't support the latest version we have.
+ proto = r.Kernel
+ }
+ c.proto = proto
+
+ s := &InitResponse{
+ Library: proto,
+ MaxReadahead: conf.maxReadahead,
+ MaxWrite: maxWrite,
+ Flags: InitBigWrites | conf.initFlags,
+ }
+ r.Respond(s)
+ return nil
+}
+
+// A Request represents a single FUSE request received from the kernel.
+// Use a type switch to determine the specific kind.
+// A request of unrecognized type will have concrete type *Header.
+type Request interface {
+ // Hdr returns the Header associated with this request.
+ Hdr() *Header
+
+ // RespondError responds to the request with the given error.
+ RespondError(error)
+
+ String() string
+}
+
+// A RequestID identifies an active FUSE request.
+type RequestID uint64
+
+func (r RequestID) String() string {
+ return fmt.Sprintf("%#x", uint64(r))
+}
+
+// A NodeID is a number identifying a directory or file.
+// It must be unique among IDs returned in LookupResponses
+// that have not yet been forgotten by ForgetRequests.
+type NodeID uint64
+
+func (n NodeID) String() string {
+ return fmt.Sprintf("%#x", uint64(n))
+}
+
+// A HandleID is a number identifying an open directory or file.
+// It only needs to be unique while the directory or file is open.
+type HandleID uint64
+
+func (h HandleID) String() string {
+ return fmt.Sprintf("%#x", uint64(h))
+}
+
+// The RootID identifies the root directory of a FUSE file system.
+const RootID NodeID = rootID
+
+// A Header describes the basic information sent in every request.
+type Header struct {
+ Conn *Conn `json:"-"` // connection this request was received on
+ ID RequestID // unique ID for request
+ Node NodeID // file or directory the request is about
+ Uid uint32 // user ID of process making request
+ Gid uint32 // group ID of process making request
+ Pid uint32 // process ID of process making request
+
+ // for returning to reqPool
+ msg *message
+}
+
+func (h *Header) String() string {
+ return fmt.Sprintf("ID=%v Node=%v Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid)
+}
+
+func (h *Header) Hdr() *Header {
+ return h
+}
+
+func (h *Header) noResponse() {
+ putMessage(h.msg)
+}
+
+func (h *Header) respond(msg []byte) {
+ out := (*outHeader)(unsafe.Pointer(&msg[0]))
+ out.Unique = uint64(h.ID)
+ h.Conn.respond(msg)
+ putMessage(h.msg)
+}
+
+// An ErrorNumber is an error with a specific error number.
+//
+// Operations may return an error value that implements ErrorNumber to
+// control what specific error number (errno) to return.
+type ErrorNumber interface {
+ // Errno returns the the error number (errno) for this error.
+ Errno() Errno
+}
+
+const (
+ // ENOSYS indicates that the call is not supported.
+ ENOSYS = Errno(syscall.ENOSYS)
+
+ // ESTALE is used by Serve to respond to violations of the FUSE protocol.
+ ESTALE = Errno(syscall.ESTALE)
+
+ ENOENT = Errno(syscall.ENOENT)
+ EIO = Errno(syscall.EIO)
+ EPERM = Errno(syscall.EPERM)
+
+ // EINTR indicates request was interrupted by an InterruptRequest.
+ // See also fs.Intr.
+ EINTR = Errno(syscall.EINTR)
+
+ ERANGE = Errno(syscall.ERANGE)
+ ENOTSUP = Errno(syscall.ENOTSUP)
+ EEXIST = Errno(syscall.EEXIST)
+)
+
+// DefaultErrno is the errno used when error returned does not
+// implement ErrorNumber.
+const DefaultErrno = EIO
+
+var errnoNames = map[Errno]string{
+ ENOSYS: "ENOSYS",
+ ESTALE: "ESTALE",
+ ENOENT: "ENOENT",
+ EIO: "EIO",
+ EPERM: "EPERM",
+ EINTR: "EINTR",
+ EEXIST: "EEXIST",
+}
+
+// Errno implements Error and ErrorNumber using a syscall.Errno.
+type Errno syscall.Errno
+
+var _ = ErrorNumber(Errno(0))
+var _ = error(Errno(0))
+
+func (e Errno) Errno() Errno {
+ return e
+}
+
+func (e Errno) String() string {
+ return syscall.Errno(e).Error()
+}
+
+func (e Errno) Error() string {
+ return syscall.Errno(e).Error()
+}
+
+// ErrnoName returns the short non-numeric identifier for this errno.
+// For example, "EIO".
+func (e Errno) ErrnoName() string {
+ s := errnoNames[e]
+ if s == "" {
+ s = fmt.Sprint(e.Errno())
+ }
+ return s
+}
+
+func (e Errno) MarshalText() ([]byte, error) {
+ s := e.ErrnoName()
+ return []byte(s), nil
+}
+
+func (h *Header) RespondError(err error) {
+ errno := DefaultErrno
+ if ferr, ok := err.(ErrorNumber); ok {
+ errno = ferr.Errno()
+ }
+ // FUSE uses negative errors!
+ // TODO: File bug report against OSXFUSE: positive error causes kernel panic.
+ buf := newBuffer(0)
+ hOut := (*outHeader)(unsafe.Pointer(&buf[0]))
+ hOut.Error = -int32(errno)
+ h.respond(buf)
+}
+
+// All requests read from the kernel, without data, are shorter than
+// this.
+var maxRequestSize = syscall.Getpagesize()
+var bufSize = maxRequestSize + maxWrite
+
+// reqPool is a pool of messages.
+//
+// Lifetime of a logical message is from getMessage to putMessage.
+// getMessage is called by ReadRequest. putMessage is called by
+// Conn.ReadRequest, Request.Respond, or Request.RespondError.
+//
+// Messages in the pool are guaranteed to have conn and off zeroed,
+// buf allocated and len==bufSize, and hdr set.
+var reqPool = sync.Pool{
+ New: allocMessage,
+}
+
+func allocMessage() interface{} {
+ m := &message{buf: make([]byte, bufSize)}
+ m.hdr = (*inHeader)(unsafe.Pointer(&m.buf[0]))
+ return m
+}
+
+func getMessage(c *Conn) *message {
+ m := reqPool.Get().(*message)
+ m.conn = c
+ return m
+}
+
+func putMessage(m *message) {
+ m.buf = m.buf[:bufSize]
+ m.conn = nil
+ m.off = 0
+ reqPool.Put(m)
+}
+
+// a message represents the bytes of a single FUSE message
+type message struct {
+ conn *Conn
+ buf []byte // all bytes
+ hdr *inHeader // header
+ off int // offset for reading additional fields
+}
+
+func (m *message) len() uintptr {
+ return uintptr(len(m.buf) - m.off)
+}
+
+func (m *message) data() unsafe.Pointer {
+ var p unsafe.Pointer
+ if m.off < len(m.buf) {
+ p = unsafe.Pointer(&m.buf[m.off])
+ }
+ return p
+}
+
+func (m *message) bytes() []byte {
+ return m.buf[m.off:]
+}
+
+func (m *message) Header() Header {
+ h := m.hdr
+ return Header{
+ Conn: m.conn,
+ ID: RequestID(h.Unique),
+ Node: NodeID(h.Nodeid),
+ Uid: h.Uid,
+ Gid: h.Gid,
+ Pid: h.Pid,
+
+ msg: m,
+ }
+}
+
+// fileMode returns a Go os.FileMode from a Unix mode.
+func fileMode(unixMode uint32) os.FileMode {
+ mode := os.FileMode(unixMode & 0777)
+ switch unixMode & syscall.S_IFMT {
+ case syscall.S_IFREG:
+ // nothing
+ case syscall.S_IFDIR:
+ mode |= os.ModeDir
+ case syscall.S_IFCHR:
+ mode |= os.ModeCharDevice | os.ModeDevice
+ case syscall.S_IFBLK:
+ mode |= os.ModeDevice
+ case syscall.S_IFIFO:
+ mode |= os.ModeNamedPipe
+ case syscall.S_IFLNK:
+ mode |= os.ModeSymlink
+ case syscall.S_IFSOCK:
+ mode |= os.ModeSocket
+ default:
+ // no idea
+ mode |= os.ModeDevice
+ }
+ if unixMode&syscall.S_ISUID != 0 {
+ mode |= os.ModeSetuid
+ }
+ if unixMode&syscall.S_ISGID != 0 {
+ mode |= os.ModeSetgid
+ }
+ return mode
+}
+
+type noOpcode struct {
+ Opcode uint32
+}
+
+func (m noOpcode) String() string {
+ return fmt.Sprintf("No opcode %v", m.Opcode)
+}
+
+type malformedMessage struct {
+}
+
+func (malformedMessage) String() string {
+ return "malformed message"
+}
+
+// Close closes the FUSE connection.
+func (c *Conn) Close() error {
+ c.wio.Lock()
+ defer c.wio.Unlock()
+ c.rio.Lock()
+ defer c.rio.Unlock()
+ return c.dev.Close()
+}
+
+// caller must hold wio or rio
+func (c *Conn) fd() int {
+ return int(c.dev.Fd())
+}
+
+func (c *Conn) Protocol() Protocol {
+ return c.proto
+}
+
+// ReadRequest returns the next FUSE request from the kernel.
+//
+// Caller must call either Request.Respond or Request.RespondError in
+// a reasonable time. Caller must not retain Request after that call.
+func (c *Conn) ReadRequest() (Request, error) {
+ m := getMessage(c)
+loop:
+ c.rio.RLock()
+ n, err := syscall.Read(c.fd(), m.buf)
+ c.rio.RUnlock()
+ if err == syscall.EINTR {
+ // OSXFUSE sends EINTR to userspace when a request interrupt
+ // completed before it got sent to userspace?
+ goto loop
+ }
+ if err != nil && err != syscall.ENODEV {
+ putMessage(m)
+ return nil, err
+ }
+ if n <= 0 {
+ putMessage(m)
+ return nil, io.EOF
+ }
+ m.buf = m.buf[:n]
+
+ if n < inHeaderSize {
+ putMessage(m)
+ return nil, errors.New("fuse: message too short")
+ }
+
+ // FreeBSD FUSE sends a short length in the header
+ // for FUSE_INIT even though the actual read length is correct.
+ if n == inHeaderSize+initInSize && m.hdr.Opcode == opInit && m.hdr.Len < uint32(n) {
+ m.hdr.Len = uint32(n)
+ }
+
+ // OSXFUSE sometimes sends the wrong m.hdr.Len in a FUSE_WRITE message.
+ if m.hdr.Len < uint32(n) && m.hdr.Len >= uint32(unsafe.Sizeof(writeIn{})) && m.hdr.Opcode == opWrite {
+ m.hdr.Len = uint32(n)
+ }
+
+ if m.hdr.Len != uint32(n) {
+ // prepare error message before returning m to pool
+ err := fmt.Errorf("fuse: read %d opcode %d but expected %d", n, m.hdr.Opcode, m.hdr.Len)
+ putMessage(m)
+ return nil, err
+ }
+
+ m.off = inHeaderSize
+
+ // Convert to data structures.
+ // Do not trust kernel to hand us well-formed data.
+ var req Request
+ switch m.hdr.Opcode {
+ default:
+ Debug(noOpcode{Opcode: m.hdr.Opcode})
+ goto unrecognized
+
+ case opLookup:
+ buf := m.bytes()
+ n := len(buf)
+ if n == 0 || buf[n-1] != '\x00' {
+ goto corrupt
+ }
+ req = &LookupRequest{
+ Header: m.Header(),
+ Name: string(buf[:n-1]),
+ }
+
+ case opForget:
+ in := (*forgetIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &ForgetRequest{
+ Header: m.Header(),
+ N: in.Nlookup,
+ }
+
+ case opGetattr:
+ switch {
+ case c.proto.LT(Protocol{7, 9}):
+ req = &GetattrRequest{
+ Header: m.Header(),
+ }
+
+ default:
+ in := (*getattrIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &GetattrRequest{
+ Header: m.Header(),
+ Flags: GetattrFlags(in.GetattrFlags),
+ Handle: HandleID(in.Fh),
+ }
+ }
+
+ case opSetattr:
+ in := (*setattrIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &SetattrRequest{
+ Header: m.Header(),
+ Valid: SetattrValid(in.Valid),
+ Handle: HandleID(in.Fh),
+ Size: in.Size,
+ Atime: time.Unix(int64(in.Atime), int64(in.AtimeNsec)),
+ Mtime: time.Unix(int64(in.Mtime), int64(in.MtimeNsec)),
+ Mode: fileMode(in.Mode),
+ Uid: in.Uid,
+ Gid: in.Gid,
+ Bkuptime: in.BkupTime(),
+ Chgtime: in.Chgtime(),
+ Flags: in.Flags(),
+ }
+
+ case opReadlink:
+ if len(m.bytes()) > 0 {
+ goto corrupt
+ }
+ req = &ReadlinkRequest{
+ Header: m.Header(),
+ }
+
+ case opSymlink:
+ // m.bytes() is "newName\0target\0"
+ names := m.bytes()
+ if len(names) == 0 || names[len(names)-1] != 0 {
+ goto corrupt
+ }
+ i := bytes.IndexByte(names, '\x00')
+ if i < 0 {
+ goto corrupt
+ }
+ newName, target := names[0:i], names[i+1:len(names)-1]
+ req = &SymlinkRequest{
+ Header: m.Header(),
+ NewName: string(newName),
+ Target: string(target),
+ }
+
+ case opLink:
+ in := (*linkIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ newName := m.bytes()[unsafe.Sizeof(*in):]
+ if len(newName) < 2 || newName[len(newName)-1] != 0 {
+ goto corrupt
+ }
+ newName = newName[:len(newName)-1]
+ req = &LinkRequest{
+ Header: m.Header(),
+ OldNode: NodeID(in.Oldnodeid),
+ NewName: string(newName),
+ }
+
+ case opMknod:
+ size := mknodInSize(c.proto)
+ if m.len() < size {
+ goto corrupt
+ }
+ in := (*mknodIn)(m.data())
+ name := m.bytes()[size:]
+ if len(name) < 2 || name[len(name)-1] != '\x00' {
+ goto corrupt
+ }
+ name = name[:len(name)-1]
+ r := &MknodRequest{
+ Header: m.Header(),
+ Mode: fileMode(in.Mode),
+ Rdev: in.Rdev,
+ Name: string(name),
+ }
+ if c.proto.GE(Protocol{7, 12}) {
+ r.Umask = fileMode(in.Umask) & os.ModePerm
+ }
+ req = r
+
+ case opMkdir:
+ size := mkdirInSize(c.proto)
+ if m.len() < size {
+ goto corrupt
+ }
+ in := (*mkdirIn)(m.data())
+ name := m.bytes()[size:]
+ i := bytes.IndexByte(name, '\x00')
+ if i < 0 {
+ goto corrupt
+ }
+ r := &MkdirRequest{
+ Header: m.Header(),
+ Name: string(name[:i]),
+ // observed on Linux: mkdirIn.Mode & syscall.S_IFMT == 0,
+ // and this causes fileMode to go into it's "no idea"
+ // code branch; enforce type to directory
+ Mode: fileMode((in.Mode &^ syscall.S_IFMT) | syscall.S_IFDIR),
+ }
+ if c.proto.GE(Protocol{7, 12}) {
+ r.Umask = fileMode(in.Umask) & os.ModePerm
+ }
+ req = r
+
+ case opUnlink, opRmdir:
+ buf := m.bytes()
+ n := len(buf)
+ if n == 0 || buf[n-1] != '\x00' {
+ goto corrupt
+ }
+ req = &RemoveRequest{
+ Header: m.Header(),
+ Name: string(buf[:n-1]),
+ Dir: m.hdr.Opcode == opRmdir,
+ }
+
+ case opRename:
+ in := (*renameIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ newDirNodeID := NodeID(in.Newdir)
+ oldNew := m.bytes()[unsafe.Sizeof(*in):]
+ // oldNew should be "old\x00new\x00"
+ if len(oldNew) < 4 {
+ goto corrupt
+ }
+ if oldNew[len(oldNew)-1] != '\x00' {
+ goto corrupt
+ }
+ i := bytes.IndexByte(oldNew, '\x00')
+ if i < 0 {
+ goto corrupt
+ }
+ oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1])
+ req = &RenameRequest{
+ Header: m.Header(),
+ NewDir: newDirNodeID,
+ OldName: oldName,
+ NewName: newName,
+ }
+
+ case opOpendir, opOpen:
+ in := (*openIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &OpenRequest{
+ Header: m.Header(),
+ Dir: m.hdr.Opcode == opOpendir,
+ Flags: openFlags(in.Flags),
+ }
+
+ case opRead, opReaddir:
+ in := (*readIn)(m.data())
+ if m.len() < readInSize(c.proto) {
+ goto corrupt
+ }
+ r := &ReadRequest{
+ Header: m.Header(),
+ Dir: m.hdr.Opcode == opReaddir,
+ Handle: HandleID(in.Fh),
+ Offset: int64(in.Offset),
+ Size: int(in.Size),
+ }
+ if c.proto.GE(Protocol{7, 9}) {
+ r.Flags = ReadFlags(in.ReadFlags)
+ r.LockOwner = in.LockOwner
+ r.FileFlags = openFlags(in.Flags)
+ }
+ req = r
+
+ case opWrite:
+ in := (*writeIn)(m.data())
+ if m.len() < writeInSize(c.proto) {
+ goto corrupt
+ }
+ r := &WriteRequest{
+ Header: m.Header(),
+ Handle: HandleID(in.Fh),
+ Offset: int64(in.Offset),
+ Flags: WriteFlags(in.WriteFlags),
+ }
+ if c.proto.GE(Protocol{7, 9}) {
+ r.LockOwner = in.LockOwner
+ r.FileFlags = openFlags(in.Flags)
+ }
+ buf := m.bytes()[writeInSize(c.proto):]
+ if uint32(len(buf)) < in.Size {
+ goto corrupt
+ }
+ r.Data = buf
+ req = r
+
+ case opStatfs:
+ req = &StatfsRequest{
+ Header: m.Header(),
+ }
+
+ case opRelease, opReleasedir:
+ in := (*releaseIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &ReleaseRequest{
+ Header: m.Header(),
+ Dir: m.hdr.Opcode == opReleasedir,
+ Handle: HandleID(in.Fh),
+ Flags: openFlags(in.Flags),
+ ReleaseFlags: ReleaseFlags(in.ReleaseFlags),
+ LockOwner: in.LockOwner,
+ }
+
+ case opFsync, opFsyncdir:
+ in := (*fsyncIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &FsyncRequest{
+ Dir: m.hdr.Opcode == opFsyncdir,
+ Header: m.Header(),
+ Handle: HandleID(in.Fh),
+ Flags: in.FsyncFlags,
+ }
+
+ case opSetxattr:
+ in := (*setxattrIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ m.off += int(unsafe.Sizeof(*in))
+ name := m.bytes()
+ i := bytes.IndexByte(name, '\x00')
+ if i < 0 {
+ goto corrupt
+ }
+ xattr := name[i+1:]
+ if uint32(len(xattr)) < in.Size {
+ goto corrupt
+ }
+ xattr = xattr[:in.Size]
+ req = &SetxattrRequest{
+ Header: m.Header(),
+ Flags: in.Flags,
+ Position: in.position(),
+ Name: string(name[:i]),
+ Xattr: xattr,
+ }
+
+ case opGetxattr:
+ in := (*getxattrIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ name := m.bytes()[unsafe.Sizeof(*in):]
+ i := bytes.IndexByte(name, '\x00')
+ if i < 0 {
+ goto corrupt
+ }
+ req = &GetxattrRequest{
+ Header: m.Header(),
+ Name: string(name[:i]),
+ Size: in.Size,
+ Position: in.position(),
+ }
+
+ case opListxattr:
+ in := (*getxattrIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &ListxattrRequest{
+ Header: m.Header(),
+ Size: in.Size,
+ Position: in.position(),
+ }
+
+ case opRemovexattr:
+ buf := m.bytes()
+ n := len(buf)
+ if n == 0 || buf[n-1] != '\x00' {
+ goto corrupt
+ }
+ req = &RemovexattrRequest{
+ Header: m.Header(),
+ Name: string(buf[:n-1]),
+ }
+
+ case opFlush:
+ in := (*flushIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &FlushRequest{
+ Header: m.Header(),
+ Handle: HandleID(in.Fh),
+ Flags: in.FlushFlags,
+ LockOwner: in.LockOwner,
+ }
+
+ case opInit:
+ in := (*initIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &InitRequest{
+ Header: m.Header(),
+ Kernel: Protocol{in.Major, in.Minor},
+ MaxReadahead: in.MaxReadahead,
+ Flags: InitFlags(in.Flags),
+ }
+
+ case opGetlk:
+ panic("opGetlk")
+ case opSetlk:
+ panic("opSetlk")
+ case opSetlkw:
+ panic("opSetlkw")
+
+ case opAccess:
+ in := (*accessIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &AccessRequest{
+ Header: m.Header(),
+ Mask: in.Mask,
+ }
+
+ case opCreate:
+ size := createInSize(c.proto)
+ if m.len() < size {
+ goto corrupt
+ }
+ in := (*createIn)(m.data())
+ name := m.bytes()[size:]
+ i := bytes.IndexByte(name, '\x00')
+ if i < 0 {
+ goto corrupt
+ }
+ r := &CreateRequest{
+ Header: m.Header(),
+ Flags: openFlags(in.Flags),
+ Mode: fileMode(in.Mode),
+ Name: string(name[:i]),
+ }
+ if c.proto.GE(Protocol{7, 12}) {
+ r.Umask = fileMode(in.Umask) & os.ModePerm
+ }
+ req = r
+
+ case opInterrupt:
+ in := (*interruptIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ req = &InterruptRequest{
+ Header: m.Header(),
+ IntrID: RequestID(in.Unique),
+ }
+
+ case opBmap:
+ panic("opBmap")
+
+ case opDestroy:
+ req = &DestroyRequest{
+ Header: m.Header(),
+ }
+
+ // OS X
+ case opSetvolname:
+ panic("opSetvolname")
+ case opGetxtimes:
+ panic("opGetxtimes")
+ case opExchange:
+ in := (*exchangeIn)(m.data())
+ if m.len() < unsafe.Sizeof(*in) {
+ goto corrupt
+ }
+ oldDirNodeID := NodeID(in.Olddir)
+ newDirNodeID := NodeID(in.Newdir)
+ oldNew := m.bytes()[unsafe.Sizeof(*in):]
+ // oldNew should be "oldname\x00newname\x00"
+ if len(oldNew) < 4 {
+ goto corrupt
+ }
+ if oldNew[len(oldNew)-1] != '\x00' {
+ goto corrupt
+ }
+ i := bytes.IndexByte(oldNew, '\x00')
+ if i < 0 {
+ goto corrupt
+ }
+ oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1])
+ req = &ExchangeDataRequest{
+ Header: m.Header(),
+ OldDir: oldDirNodeID,
+ NewDir: newDirNodeID,
+ OldName: oldName,
+ NewName: newName,
+ // TODO options
+ }
+ }
+
+ return req, nil
+
+corrupt:
+ Debug(malformedMessage{})
+ putMessage(m)
+ return nil, fmt.Errorf("fuse: malformed message")
+
+unrecognized:
+ // Unrecognized message.
+ // Assume higher-level code will send a "no idea what you mean" error.
+ h := m.Header()
+ return &h, nil
+}
+
+type bugShortKernelWrite struct {
+ Written int64
+ Length int64
+ Error string
+ Stack string
+}
+
+func (b bugShortKernelWrite) String() string {
+ return fmt.Sprintf("short kernel write: written=%d/%d error=%q stack=\n%s", b.Written, b.Length, b.Error, b.Stack)
+}
+
+type bugKernelWriteError struct {
+ Error string
+ Stack string
+}
+
+func (b bugKernelWriteError) String() string {
+ return fmt.Sprintf("kernel write error: error=%q stack=\n%s", b.Error, b.Stack)
+}
+
+// safe to call even with nil error
+func errorString(err error) string {
+ if err == nil {
+ return ""
+ }
+ return err.Error()
+}
+
+func (c *Conn) writeToKernel(msg []byte) error {
+ out := (*outHeader)(unsafe.Pointer(&msg[0]))
+ out.Len = uint32(len(msg))
+
+ c.wio.RLock()
+ defer c.wio.RUnlock()
+ nn, err := syscall.Write(c.fd(), msg)
+ if err == nil && nn != len(msg) {
+ Debug(bugShortKernelWrite{
+ Written: int64(nn),
+ Length: int64(len(msg)),
+ Error: errorString(err),
+ Stack: stack(),
+ })
+ }
+ return err
+}
+
+func (c *Conn) respond(msg []byte) {
+ if err := c.writeToKernel(msg); err != nil {
+ Debug(bugKernelWriteError{
+ Error: errorString(err),
+ Stack: stack(),
+ })
+ }
+}
+
+type notCachedError struct{}
+
+func (notCachedError) Error() string {
+ return "node not cached"
+}
+
+var _ ErrorNumber = notCachedError{}
+
+func (notCachedError) Errno() Errno {
+ // Behave just like if the original syscall.ENOENT had been passed
+ // straight through.
+ return ENOENT
+}
+
+var (
+ ErrNotCached = notCachedError{}
+)
+
+// sendInvalidate sends an invalidate notification to kernel.
+//
+// A returned ENOENT is translated to a friendlier error.
+func (c *Conn) sendInvalidate(msg []byte) error {
+ switch err := c.writeToKernel(msg); err {
+ case syscall.ENOENT:
+ return ErrNotCached
+ default:
+ return err
+ }
+}
+
+// InvalidateNode invalidates the kernel cache of the attributes and a
+// range of the data of a node.
+//
+// Giving offset 0 and size -1 means all data. To invalidate just the
+// attributes, give offset 0 and size 0.
+//
+// Returns ErrNotCached if the kernel is not currently caching the
+// node.
+func (c *Conn) InvalidateNode(nodeID NodeID, off int64, size int64) error {
+ buf := newBuffer(unsafe.Sizeof(notifyInvalInodeOut{}))
+ h := (*outHeader)(unsafe.Pointer(&buf[0]))
+ // h.Unique is 0
+ h.Error = notifyCodeInvalInode
+ out := (*notifyInvalInodeOut)(buf.alloc(unsafe.Sizeof(notifyInvalInodeOut{})))
+ out.Ino = uint64(nodeID)
+ out.Off = off
+ out.Len = size
+ return c.sendInvalidate(buf)
+}
+
+// InvalidateEntry invalidates the kernel cache of the directory entry
+// identified by parent directory node ID and entry basename.
+//
+// Kernel may or may not cache directory listings. To invalidate
+// those, use InvalidateNode to invalidate all of the data for a
+// directory. (As of 2015-06, Linux FUSE does not cache directory
+// listings.)
+//
+// Returns ErrNotCached if the kernel is not currently caching the
+// node.
+func (c *Conn) InvalidateEntry(parent NodeID, name string) error {
+ const maxUint32 = ^uint32(0)
+ if uint64(len(name)) > uint64(maxUint32) {
+ // very unlikely, but we don't want to silently truncate
+ return syscall.ENAMETOOLONG
+ }
+ buf := newBuffer(unsafe.Sizeof(notifyInvalEntryOut{}) + uintptr(len(name)) + 1)
+ h := (*outHeader)(unsafe.Pointer(&buf[0]))
+ // h.Unique is 0
+ h.Error = notifyCodeInvalEntry
+ out := (*notifyInvalEntryOut)(buf.alloc(unsafe.Sizeof(notifyInvalEntryOut{})))
+ out.Parent = uint64(parent)
+ out.Namelen = uint32(len(name))
+ buf = append(buf, name...)
+ buf = append(buf, '\x00')
+ return c.sendInvalidate(buf)
+}
+
+// An InitRequest is the first request sent on a FUSE file system.
+type InitRequest struct {
+ Header `json:"-"`
+ Kernel Protocol
+ // Maximum readahead in bytes that the kernel plans to use.
+ MaxReadahead uint32
+ Flags InitFlags
+}
+
+var _ = Request(&InitRequest{})
+
+func (r *InitRequest) String() string {
+ return fmt.Sprintf("Init [%v] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags)
+}
+
+// An InitResponse is the response to an InitRequest.
+type InitResponse struct {
+ Library Protocol
+ // Maximum readahead in bytes that the kernel can use. Ignored if
+ // greater than InitRequest.MaxReadahead.
+ MaxReadahead uint32
+ Flags InitFlags
+ // Maximum size of a single write operation.
+ // Linux enforces a minimum of 4 KiB.
+ MaxWrite uint32
+}
+
+func (r *InitResponse) String() string {
+ return fmt.Sprintf("Init %v ra=%d fl=%v w=%d", r.Library, r.MaxReadahead, r.Flags, r.MaxWrite)
+}
+
+// Respond replies to the request with the given response.
+func (r *InitRequest) Respond(resp *InitResponse) {
+ buf := newBuffer(unsafe.Sizeof(initOut{}))
+ out := (*initOut)(buf.alloc(unsafe.Sizeof(initOut{})))
+ out.Major = resp.Library.Major
+ out.Minor = resp.Library.Minor
+ out.MaxReadahead = resp.MaxReadahead
+ out.Flags = uint32(resp.Flags)
+ out.MaxWrite = resp.MaxWrite
+
+ // MaxWrite larger than our receive buffer would just lead to
+ // errors on large writes.
+ if out.MaxWrite > maxWrite {
+ out.MaxWrite = maxWrite
+ }
+ r.respond(buf)
+}
+
+// A StatfsRequest requests information about the mounted file system.
+type StatfsRequest struct {
+ Header `json:"-"`
+}
+
+var _ = Request(&StatfsRequest{})
+
+func (r *StatfsRequest) String() string {
+ return fmt.Sprintf("Statfs [%s]", &r.Header)
+}
+
+// Respond replies to the request with the given response.
+func (r *StatfsRequest) Respond(resp *StatfsResponse) {
+ buf := newBuffer(unsafe.Sizeof(statfsOut{}))
+ out := (*statfsOut)(buf.alloc(unsafe.Sizeof(statfsOut{})))
+ out.St = kstatfs{
+ Blocks: resp.Blocks,
+ Bfree: resp.Bfree,
+ Bavail: resp.Bavail,
+ Files: resp.Files,
+ Bsize: resp.Bsize,
+ Namelen: resp.Namelen,
+ Frsize: resp.Frsize,
+ }
+ r.respond(buf)
+}
+
+// A StatfsResponse is the response to a StatfsRequest.
+type StatfsResponse struct {
+ Blocks uint64 // Total data blocks in file system.
+ Bfree uint64 // Free blocks in file system.
+ Bavail uint64 // Free blocks in file system if you're not root.
+ Files uint64 // Total files in file system.
+ Ffree uint64 // Free files in file system.
+ Bsize uint32 // Block size
+ Namelen uint32 // Maximum file name length?
+ Frsize uint32 // Fragment size, smallest addressable data size in the file system.
+}
+
+func (r *StatfsResponse) String() string {
+ return fmt.Sprintf("Statfs blocks=%d/%d/%d files=%d/%d bsize=%d frsize=%d namelen=%d",
+ r.Bavail, r.Bfree, r.Blocks,
+ r.Ffree, r.Files,
+ r.Bsize,
+ r.Frsize,
+ r.Namelen,
+ )
+}
+
+// An AccessRequest asks whether the file can be accessed
+// for the purpose specified by the mask.
+type AccessRequest struct {
+ Header `json:"-"`
+ Mask uint32
+}
+
+var _ = Request(&AccessRequest{})
+
+func (r *AccessRequest) String() string {
+ return fmt.Sprintf("Access [%s] mask=%#x", &r.Header, r.Mask)
+}
+
+// Respond replies to the request indicating that access is allowed.
+// To deny access, use RespondError.
+func (r *AccessRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+// An Attr is the metadata for a single file or directory.
+type Attr struct {
+ Valid time.Duration // how long Attr can be cached
+
+ Inode uint64 // inode number
+ Size uint64 // size in bytes
+ Blocks uint64 // size in 512-byte units
+ Atime time.Time // time of last access
+ Mtime time.Time // time of last modification
+ Ctime time.Time // time of last inode change
+ Crtime time.Time // time of creation (OS X only)
+ Mode os.FileMode // file mode
+ Nlink uint32 // number of links (usually 1)
+ Uid uint32 // owner uid
+ Gid uint32 // group gid
+ Rdev uint32 // device numbers
+ Flags uint32 // chflags(2) flags (OS X only)
+ BlockSize uint32 // preferred blocksize for filesystem I/O
+}
+
+func (a Attr) String() string {
+ return fmt.Sprintf("valid=%v ino=%v size=%d mode=%v", a.Valid, a.Inode, a.Size, a.Mode)
+}
+
+func unix(t time.Time) (sec uint64, nsec uint32) {
+ nano := t.UnixNano()
+ sec = uint64(nano / 1e9)
+ nsec = uint32(nano % 1e9)
+ return
+}
+
+func (a *Attr) attr(out *attr, proto Protocol) {
+ out.Ino = a.Inode
+ out.Size = a.Size
+ out.Blocks = a.Blocks
+ out.Atime, out.AtimeNsec = unix(a.Atime)
+ out.Mtime, out.MtimeNsec = unix(a.Mtime)
+ out.Ctime, out.CtimeNsec = unix(a.Ctime)
+ out.SetCrtime(unix(a.Crtime))
+ out.Mode = uint32(a.Mode) & 0777
+ switch {
+ default:
+ out.Mode |= syscall.S_IFREG
+ case a.Mode&os.ModeDir != 0:
+ out.Mode |= syscall.S_IFDIR
+ case a.Mode&os.ModeDevice != 0:
+ if a.Mode&os.ModeCharDevice != 0 {
+ out.Mode |= syscall.S_IFCHR
+ } else {
+ out.Mode |= syscall.S_IFBLK
+ }
+ case a.Mode&os.ModeNamedPipe != 0:
+ out.Mode |= syscall.S_IFIFO
+ case a.Mode&os.ModeSymlink != 0:
+ out.Mode |= syscall.S_IFLNK
+ case a.Mode&os.ModeSocket != 0:
+ out.Mode |= syscall.S_IFSOCK
+ }
+ if a.Mode&os.ModeSetuid != 0 {
+ out.Mode |= syscall.S_ISUID
+ }
+ if a.Mode&os.ModeSetgid != 0 {
+ out.Mode |= syscall.S_ISGID
+ }
+ out.Nlink = a.Nlink
+ out.Uid = a.Uid
+ out.Gid = a.Gid
+ out.Rdev = a.Rdev
+ out.SetFlags(a.Flags)
+ if proto.GE(Protocol{7, 9}) {
+ out.Blksize = a.BlockSize
+ }
+
+ return
+}
+
+// A GetattrRequest asks for the metadata for the file denoted by r.Node.
+type GetattrRequest struct {
+ Header `json:"-"`
+ Flags GetattrFlags
+ Handle HandleID
+}
+
+var _ = Request(&GetattrRequest{})
+
+func (r *GetattrRequest) String() string {
+ return fmt.Sprintf("Getattr [%s] %v fl=%v", &r.Header, r.Handle, r.Flags)
+}
+
+// Respond replies to the request with the given response.
+func (r *GetattrRequest) Respond(resp *GetattrResponse) {
+ size := attrOutSize(r.Header.Conn.proto)
+ buf := newBuffer(size)
+ out := (*attrOut)(buf.alloc(size))
+ out.AttrValid = uint64(resp.Attr.Valid / time.Second)
+ out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
+ resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
+ r.respond(buf)
+}
+
+// A GetattrResponse is the response to a GetattrRequest.
+type GetattrResponse struct {
+ Attr Attr // file attributes
+}
+
+func (r *GetattrResponse) String() string {
+ return fmt.Sprintf("Getattr %v", r.Attr)
+}
+
+// A GetxattrRequest asks for the extended attributes associated with r.Node.
+type GetxattrRequest struct {
+ Header `json:"-"`
+
+ // Maximum size to return.
+ Size uint32
+
+ // Name of the attribute requested.
+ Name string
+
+ // Offset within extended attributes.
+ //
+ // Only valid for OS X, and then only with the resource fork
+ // attribute.
+ Position uint32
+}
+
+var _ = Request(&GetxattrRequest{})
+
+func (r *GetxattrRequest) String() string {
+ return fmt.Sprintf("Getxattr [%s] %q %d @%d", &r.Header, r.Name, r.Size, r.Position)
+}
+
+// Respond replies to the request with the given response.
+func (r *GetxattrRequest) Respond(resp *GetxattrResponse) {
+ if r.Size == 0 {
+ buf := newBuffer(unsafe.Sizeof(getxattrOut{}))
+ out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{})))
+ out.Size = uint32(len(resp.Xattr))
+ r.respond(buf)
+ } else {
+ buf := newBuffer(uintptr(len(resp.Xattr)))
+ buf = append(buf, resp.Xattr...)
+ r.respond(buf)
+ }
+}
+
+// A GetxattrResponse is the response to a GetxattrRequest.
+type GetxattrResponse struct {
+ Xattr []byte
+}
+
+func (r *GetxattrResponse) String() string {
+ return fmt.Sprintf("Getxattr %x", r.Xattr)
+}
+
+// A ListxattrRequest asks to list the extended attributes associated with r.Node.
+type ListxattrRequest struct {
+ Header `json:"-"`
+ Size uint32 // maximum size to return
+ Position uint32 // offset within attribute list
+}
+
+var _ = Request(&ListxattrRequest{})
+
+func (r *ListxattrRequest) String() string {
+ return fmt.Sprintf("Listxattr [%s] %d @%d", &r.Header, r.Size, r.Position)
+}
+
+// Respond replies to the request with the given response.
+func (r *ListxattrRequest) Respond(resp *ListxattrResponse) {
+ if r.Size == 0 {
+ buf := newBuffer(unsafe.Sizeof(getxattrOut{}))
+ out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{})))
+ out.Size = uint32(len(resp.Xattr))
+ r.respond(buf)
+ } else {
+ buf := newBuffer(uintptr(len(resp.Xattr)))
+ buf = append(buf, resp.Xattr...)
+ r.respond(buf)
+ }
+}
+
+// A ListxattrResponse is the response to a ListxattrRequest.
+type ListxattrResponse struct {
+ Xattr []byte
+}
+
+func (r *ListxattrResponse) String() string {
+ return fmt.Sprintf("Listxattr %x", r.Xattr)
+}
+
+// Append adds an extended attribute name to the response.
+func (r *ListxattrResponse) Append(names ...string) {
+ for _, name := range names {
+ r.Xattr = append(r.Xattr, name...)
+ r.Xattr = append(r.Xattr, '\x00')
+ }
+}
+
+// A RemovexattrRequest asks to remove an extended attribute associated with r.Node.
+type RemovexattrRequest struct {
+ Header `json:"-"`
+ Name string // name of extended attribute
+}
+
+var _ = Request(&RemovexattrRequest{})
+
+func (r *RemovexattrRequest) String() string {
+ return fmt.Sprintf("Removexattr [%s] %q", &r.Header, r.Name)
+}
+
+// Respond replies to the request, indicating that the attribute was removed.
+func (r *RemovexattrRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+// A SetxattrRequest asks to set an extended attribute associated with a file.
+type SetxattrRequest struct {
+ Header `json:"-"`
+
+ // Flags can make the request fail if attribute does/not already
+ // exist. Unfortunately, the constants are platform-specific and
+ // not exposed by Go1.2. Look for XATTR_CREATE, XATTR_REPLACE.
+ //
+ // TODO improve this later
+ //
+ // TODO XATTR_CREATE and exist -> EEXIST
+ //
+ // TODO XATTR_REPLACE and not exist -> ENODATA
+ Flags uint32
+
+ // Offset within extended attributes.
+ //
+ // Only valid for OS X, and then only with the resource fork
+ // attribute.
+ Position uint32
+
+ Name string
+ Xattr []byte
+}
+
+var _ = Request(&SetxattrRequest{})
+
+func trunc(b []byte, max int) ([]byte, string) {
+ if len(b) > max {
+ return b[:max], "..."
+ }
+ return b, ""
+}
+
+func (r *SetxattrRequest) String() string {
+ xattr, tail := trunc(r.Xattr, 16)
+ return fmt.Sprintf("Setxattr [%s] %q %x%s fl=%v @%#x", &r.Header, r.Name, xattr, tail, r.Flags, r.Position)
+}
+
+// Respond replies to the request, indicating that the extended attribute was set.
+func (r *SetxattrRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+// A LookupRequest asks to look up the given name in the directory named by r.Node.
+type LookupRequest struct {
+ Header `json:"-"`
+ Name string
+}
+
+var _ = Request(&LookupRequest{})
+
+func (r *LookupRequest) String() string {
+ return fmt.Sprintf("Lookup [%s] %q", &r.Header, r.Name)
+}
+
+// Respond replies to the request with the given response.
+func (r *LookupRequest) Respond(resp *LookupResponse) {
+ size := entryOutSize(r.Header.Conn.proto)
+ buf := newBuffer(size)
+ out := (*entryOut)(buf.alloc(size))
+ out.Nodeid = uint64(resp.Node)
+ out.Generation = resp.Generation
+ out.EntryValid = uint64(resp.EntryValid / time.Second)
+ out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
+ out.AttrValid = uint64(resp.Attr.Valid / time.Second)
+ out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
+ resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
+ r.respond(buf)
+}
+
+// A LookupResponse is the response to a LookupRequest.
+type LookupResponse struct {
+ Node NodeID
+ Generation uint64
+ EntryValid time.Duration
+ Attr Attr
+}
+
+func (r *LookupResponse) string() string {
+ return fmt.Sprintf("%v gen=%d valid=%v attr={%v}", r.Node, r.Generation, r.EntryValid, r.Attr)
+}
+
+func (r *LookupResponse) String() string {
+ return fmt.Sprintf("Lookup %s", r.string())
+}
+
+// An OpenRequest asks to open a file or directory
+type OpenRequest struct {
+ Header `json:"-"`
+ Dir bool // is this Opendir?
+ Flags OpenFlags
+}
+
+var _ = Request(&OpenRequest{})
+
+func (r *OpenRequest) String() string {
+ return fmt.Sprintf("Open [%s] dir=%v fl=%v", &r.Header, r.Dir, r.Flags)
+}
+
+// Respond replies to the request with the given response.
+func (r *OpenRequest) Respond(resp *OpenResponse) {
+ buf := newBuffer(unsafe.Sizeof(openOut{}))
+ out := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{})))
+ out.Fh = uint64(resp.Handle)
+ out.OpenFlags = uint32(resp.Flags)
+ r.respond(buf)
+}
+
+// A OpenResponse is the response to a OpenRequest.
+type OpenResponse struct {
+ Handle HandleID
+ Flags OpenResponseFlags
+}
+
+func (r *OpenResponse) string() string {
+ return fmt.Sprintf("%v fl=%v", r.Handle, r.Flags)
+}
+
+func (r *OpenResponse) String() string {
+ return fmt.Sprintf("Open %s", r.string())
+}
+
+// A CreateRequest asks to create and open a file (not a directory).
+type CreateRequest struct {
+ Header `json:"-"`
+ Name string
+ Flags OpenFlags
+ Mode os.FileMode
+ // Umask of the request. Not supported on OS X.
+ Umask os.FileMode
+}
+
+var _ = Request(&CreateRequest{})
+
+func (r *CreateRequest) String() string {
+ return fmt.Sprintf("Create [%s] %q fl=%v mode=%v umask=%v", &r.Header, r.Name, r.Flags, r.Mode, r.Umask)
+}
+
+// Respond replies to the request with the given response.
+func (r *CreateRequest) Respond(resp *CreateResponse) {
+ eSize := entryOutSize(r.Header.Conn.proto)
+ buf := newBuffer(eSize + unsafe.Sizeof(openOut{}))
+
+ e := (*entryOut)(buf.alloc(eSize))
+ e.Nodeid = uint64(resp.Node)
+ e.Generation = resp.Generation
+ e.EntryValid = uint64(resp.EntryValid / time.Second)
+ e.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
+ e.AttrValid = uint64(resp.Attr.Valid / time.Second)
+ e.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
+ resp.Attr.attr(&e.Attr, r.Header.Conn.proto)
+
+ o := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{})))
+ o.Fh = uint64(resp.Handle)
+ o.OpenFlags = uint32(resp.Flags)
+
+ r.respond(buf)
+}
+
+// A CreateResponse is the response to a CreateRequest.
+// It describes the created node and opened handle.
+type CreateResponse struct {
+ LookupResponse
+ OpenResponse
+}
+
+func (r *CreateResponse) String() string {
+ return fmt.Sprintf("Create {%s} {%s}", r.LookupResponse.string(), r.OpenResponse.string())
+}
+
+// A MkdirRequest asks to create (but not open) a directory.
+type MkdirRequest struct {
+ Header `json:"-"`
+ Name string
+ Mode os.FileMode
+ // Umask of the request. Not supported on OS X.
+ Umask os.FileMode
+}
+
+var _ = Request(&MkdirRequest{})
+
+func (r *MkdirRequest) String() string {
+ return fmt.Sprintf("Mkdir [%s] %q mode=%v umask=%v", &r.Header, r.Name, r.Mode, r.Umask)
+}
+
+// Respond replies to the request with the given response.
+func (r *MkdirRequest) Respond(resp *MkdirResponse) {
+ size := entryOutSize(r.Header.Conn.proto)
+ buf := newBuffer(size)
+ out := (*entryOut)(buf.alloc(size))
+ out.Nodeid = uint64(resp.Node)
+ out.Generation = resp.Generation
+ out.EntryValid = uint64(resp.EntryValid / time.Second)
+ out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
+ out.AttrValid = uint64(resp.Attr.Valid / time.Second)
+ out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
+ resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
+ r.respond(buf)
+}
+
+// A MkdirResponse is the response to a MkdirRequest.
+type MkdirResponse struct {
+ LookupResponse
+}
+
+func (r *MkdirResponse) String() string {
+ return fmt.Sprintf("Mkdir %v", r.LookupResponse.string())
+}
+
+// A ReadRequest asks to read from an open file.
+type ReadRequest struct {
+ Header `json:"-"`
+ Dir bool // is this Readdir?
+ Handle HandleID
+ Offset int64
+ Size int
+ Flags ReadFlags
+ LockOwner uint64
+ FileFlags OpenFlags
+}
+
+var _ = Request(&ReadRequest{})
+
+func (r *ReadRequest) String() string {
+ return fmt.Sprintf("Read [%s] %v %d @%#x dir=%v fl=%v lock=%d ffl=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir, r.Flags, r.LockOwner, r.FileFlags)
+}
+
+// Respond replies to the request with the given response.
+func (r *ReadRequest) Respond(resp *ReadResponse) {
+ buf := newBuffer(uintptr(len(resp.Data)))
+ buf = append(buf, resp.Data...)
+ r.respond(buf)
+}
+
+// A ReadResponse is the response to a ReadRequest.
+type ReadResponse struct {
+ Data []byte
+}
+
+func (r *ReadResponse) String() string {
+ return fmt.Sprintf("Read %d", len(r.Data))
+}
+
+type jsonReadResponse struct {
+ Len uint64
+}
+
+func (r *ReadResponse) MarshalJSON() ([]byte, error) {
+ j := jsonReadResponse{
+ Len: uint64(len(r.Data)),
+ }
+ return json.Marshal(j)
+}
+
+// A ReleaseRequest asks to release (close) an open file handle.
+type ReleaseRequest struct {
+ Header `json:"-"`
+ Dir bool // is this Releasedir?
+ Handle HandleID
+ Flags OpenFlags // flags from OpenRequest
+ ReleaseFlags ReleaseFlags
+ LockOwner uint32
+}
+
+var _ = Request(&ReleaseRequest{})
+
+func (r *ReleaseRequest) String() string {
+ return fmt.Sprintf("Release [%s] %v fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner)
+}
+
+// Respond replies to the request, indicating that the handle has been released.
+func (r *ReleaseRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+// A DestroyRequest is sent by the kernel when unmounting the file system.
+// No more requests will be received after this one, but it should still be
+// responded to.
+type DestroyRequest struct {
+ Header `json:"-"`
+}
+
+var _ = Request(&DestroyRequest{})
+
+func (r *DestroyRequest) String() string {
+ return fmt.Sprintf("Destroy [%s]", &r.Header)
+}
+
+// Respond replies to the request.
+func (r *DestroyRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+// A ForgetRequest is sent by the kernel when forgetting about r.Node
+// as returned by r.N lookup requests.
+type ForgetRequest struct {
+ Header `json:"-"`
+ N uint64
+}
+
+var _ = Request(&ForgetRequest{})
+
+func (r *ForgetRequest) String() string {
+ return fmt.Sprintf("Forget [%s] %d", &r.Header, r.N)
+}
+
+// Respond replies to the request, indicating that the forgetfulness has been recorded.
+func (r *ForgetRequest) Respond() {
+ // Don't reply to forget messages.
+ r.noResponse()
+}
+
+// A Dirent represents a single directory entry.
+type Dirent struct {
+ // Inode this entry names.
+ Inode uint64
+
+ // Type of the entry, for example DT_File.
+ //
+ // Setting this is optional. The zero value (DT_Unknown) means
+ // callers will just need to do a Getattr when the type is
+ // needed. Providing a type can speed up operations
+ // significantly.
+ Type DirentType
+
+ // Name of the entry
+ Name string
+}
+
+// Type of an entry in a directory listing.
+type DirentType uint32
+
+const (
+ // These don't quite match os.FileMode; especially there's an
+ // explicit unknown, instead of zero value meaning file. They
+ // are also not quite syscall.DT_*; nothing says the FUSE
+ // protocol follows those, and even if they were, we don't
+ // want each fs to fiddle with syscall.
+
+ // The shift by 12 is hardcoded in the FUSE userspace
+ // low-level C library, so it's safe here.
+
+ DT_Unknown DirentType = 0
+ DT_Socket DirentType = syscall.S_IFSOCK >> 12
+ DT_Link DirentType = syscall.S_IFLNK >> 12
+ DT_File DirentType = syscall.S_IFREG >> 12
+ DT_Block DirentType = syscall.S_IFBLK >> 12
+ DT_Dir DirentType = syscall.S_IFDIR >> 12
+ DT_Char DirentType = syscall.S_IFCHR >> 12
+ DT_FIFO DirentType = syscall.S_IFIFO >> 12
+)
+
+func (t DirentType) String() string {
+ switch t {
+ case DT_Unknown:
+ return "unknown"
+ case DT_Socket:
+ return "socket"
+ case DT_Link:
+ return "link"
+ case DT_File:
+ return "file"
+ case DT_Block:
+ return "block"
+ case DT_Dir:
+ return "dir"
+ case DT_Char:
+ return "char"
+ case DT_FIFO:
+ return "fifo"
+ }
+ return "invalid"
+}
+
+// AppendDirent appends the encoded form of a directory entry to data
+// and returns the resulting slice.
+func AppendDirent(data []byte, dir Dirent) []byte {
+ de := dirent{
+ Ino: dir.Inode,
+ Namelen: uint32(len(dir.Name)),
+ Type: uint32(dir.Type),
+ }
+ de.Off = uint64(len(data) + direntSize + (len(dir.Name)+7)&^7)
+ data = append(data, (*[direntSize]byte)(unsafe.Pointer(&de))[:]...)
+ data = append(data, dir.Name...)
+ n := direntSize + uintptr(len(dir.Name))
+ if n%8 != 0 {
+ var pad [8]byte
+ data = append(data, pad[:8-n%8]...)
+ }
+ return data
+}
+
+// A WriteRequest asks to write to an open file.
+type WriteRequest struct {
+ Header
+ Handle HandleID
+ Offset int64
+ Data []byte
+ Flags WriteFlags
+ LockOwner uint64
+ FileFlags OpenFlags
+}
+
+var _ = Request(&WriteRequest{})
+
+func (r *WriteRequest) String() string {
+ return fmt.Sprintf("Write [%s] %v %d @%d fl=%v lock=%d ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags)
+}
+
+type jsonWriteRequest struct {
+ Handle HandleID
+ Offset int64
+ Len uint64
+ Flags WriteFlags
+}
+
+func (r *WriteRequest) MarshalJSON() ([]byte, error) {
+ j := jsonWriteRequest{
+ Handle: r.Handle,
+ Offset: r.Offset,
+ Len: uint64(len(r.Data)),
+ Flags: r.Flags,
+ }
+ return json.Marshal(j)
+}
+
+// Respond replies to the request with the given response.
+func (r *WriteRequest) Respond(resp *WriteResponse) {
+ buf := newBuffer(unsafe.Sizeof(writeOut{}))
+ out := (*writeOut)(buf.alloc(unsafe.Sizeof(writeOut{})))
+ out.Size = uint32(resp.Size)
+ r.respond(buf)
+}
+
+// A WriteResponse replies to a write indicating how many bytes were written.
+type WriteResponse struct {
+ Size int
+}
+
+func (r *WriteResponse) String() string {
+ return fmt.Sprintf("Write %d", r.Size)
+}
+
+// A SetattrRequest asks to change one or more attributes associated with a file,
+// as indicated by Valid.
+type SetattrRequest struct {
+ Header `json:"-"`
+ Valid SetattrValid
+ Handle HandleID
+ Size uint64
+ Atime time.Time
+ Mtime time.Time
+ Mode os.FileMode
+ Uid uint32
+ Gid uint32
+
+ // OS X only
+ Bkuptime time.Time
+ Chgtime time.Time
+ Crtime time.Time
+ Flags uint32 // see chflags(2)
+}
+
+var _ = Request(&SetattrRequest{})
+
+func (r *SetattrRequest) String() string {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "Setattr [%s]", &r.Header)
+ if r.Valid.Mode() {
+ fmt.Fprintf(&buf, " mode=%v", r.Mode)
+ }
+ if r.Valid.Uid() {
+ fmt.Fprintf(&buf, " uid=%d", r.Uid)
+ }
+ if r.Valid.Gid() {
+ fmt.Fprintf(&buf, " gid=%d", r.Gid)
+ }
+ if r.Valid.Size() {
+ fmt.Fprintf(&buf, " size=%d", r.Size)
+ }
+ if r.Valid.Atime() {
+ fmt.Fprintf(&buf, " atime=%v", r.Atime)
+ }
+ if r.Valid.AtimeNow() {
+ fmt.Fprintf(&buf, " atime=now")
+ }
+ if r.Valid.Mtime() {
+ fmt.Fprintf(&buf, " mtime=%v", r.Mtime)
+ }
+ if r.Valid.MtimeNow() {
+ fmt.Fprintf(&buf, " mtime=now")
+ }
+ if r.Valid.Handle() {
+ fmt.Fprintf(&buf, " handle=%v", r.Handle)
+ } else {
+ fmt.Fprintf(&buf, " handle=INVALID-%v", r.Handle)
+ }
+ if r.Valid.LockOwner() {
+ fmt.Fprintf(&buf, " lockowner")
+ }
+ if r.Valid.Crtime() {
+ fmt.Fprintf(&buf, " crtime=%v", r.Crtime)
+ }
+ if r.Valid.Chgtime() {
+ fmt.Fprintf(&buf, " chgtime=%v", r.Chgtime)
+ }
+ if r.Valid.Bkuptime() {
+ fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime)
+ }
+ if r.Valid.Flags() {
+ fmt.Fprintf(&buf, " flags=%v", r.Flags)
+ }
+ return buf.String()
+}
+
+// Respond replies to the request with the given response,
+// giving the updated attributes.
+func (r *SetattrRequest) Respond(resp *SetattrResponse) {
+ size := attrOutSize(r.Header.Conn.proto)
+ buf := newBuffer(size)
+ out := (*attrOut)(buf.alloc(size))
+ out.AttrValid = uint64(resp.Attr.Valid / time.Second)
+ out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
+ resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
+ r.respond(buf)
+}
+
+// A SetattrResponse is the response to a SetattrRequest.
+type SetattrResponse struct {
+ Attr Attr // file attributes
+}
+
+func (r *SetattrResponse) String() string {
+ return fmt.Sprintf("Setattr %v", r.Attr)
+}
+
+// A FlushRequest asks for the current state of an open file to be flushed
+// to storage, as when a file descriptor is being closed. A single opened Handle
+// may receive multiple FlushRequests over its lifetime.
+type FlushRequest struct {
+ Header `json:"-"`
+ Handle HandleID
+ Flags uint32
+ LockOwner uint64
+}
+
+var _ = Request(&FlushRequest{})
+
+func (r *FlushRequest) String() string {
+ return fmt.Sprintf("Flush [%s] %v fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner)
+}
+
+// Respond replies to the request, indicating that the flush succeeded.
+func (r *FlushRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+// A RemoveRequest asks to remove a file or directory from the
+// directory r.Node.
+type RemoveRequest struct {
+ Header `json:"-"`
+ Name string // name of the entry to remove
+ Dir bool // is this rmdir?
+}
+
+var _ = Request(&RemoveRequest{})
+
+func (r *RemoveRequest) String() string {
+ return fmt.Sprintf("Remove [%s] %q dir=%v", &r.Header, r.Name, r.Dir)
+}
+
+// Respond replies to the request, indicating that the file was removed.
+func (r *RemoveRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+// A SymlinkRequest is a request to create a symlink making NewName point to Target.
+type SymlinkRequest struct {
+ Header `json:"-"`
+ NewName, Target string
+}
+
+var _ = Request(&SymlinkRequest{})
+
+func (r *SymlinkRequest) String() string {
+ return fmt.Sprintf("Symlink [%s] from %q to target %q", &r.Header, r.NewName, r.Target)
+}
+
+// Respond replies to the request, indicating that the symlink was created.
+func (r *SymlinkRequest) Respond(resp *SymlinkResponse) {
+ size := entryOutSize(r.Header.Conn.proto)
+ buf := newBuffer(size)
+ out := (*entryOut)(buf.alloc(size))
+ out.Nodeid = uint64(resp.Node)
+ out.Generation = resp.Generation
+ out.EntryValid = uint64(resp.EntryValid / time.Second)
+ out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
+ out.AttrValid = uint64(resp.Attr.Valid / time.Second)
+ out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
+ resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
+ r.respond(buf)
+}
+
+// A SymlinkResponse is the response to a SymlinkRequest.
+type SymlinkResponse struct {
+ LookupResponse
+}
+
+func (r *SymlinkResponse) String() string {
+ return fmt.Sprintf("Symlink %v", r.LookupResponse.string())
+}
+
+// A ReadlinkRequest is a request to read a symlink's target.
+type ReadlinkRequest struct {
+ Header `json:"-"`
+}
+
+var _ = Request(&ReadlinkRequest{})
+
+func (r *ReadlinkRequest) String() string {
+ return fmt.Sprintf("Readlink [%s]", &r.Header)
+}
+
+func (r *ReadlinkRequest) Respond(target string) {
+ buf := newBuffer(uintptr(len(target)))
+ buf = append(buf, target...)
+ r.respond(buf)
+}
+
+// A LinkRequest is a request to create a hard link.
+type LinkRequest struct {
+ Header `json:"-"`
+ OldNode NodeID
+ NewName string
+}
+
+var _ = Request(&LinkRequest{})
+
+func (r *LinkRequest) String() string {
+ return fmt.Sprintf("Link [%s] node %d to %q", &r.Header, r.OldNode, r.NewName)
+}
+
+func (r *LinkRequest) Respond(resp *LookupResponse) {
+ size := entryOutSize(r.Header.Conn.proto)
+ buf := newBuffer(size)
+ out := (*entryOut)(buf.alloc(size))
+ out.Nodeid = uint64(resp.Node)
+ out.Generation = resp.Generation
+ out.EntryValid = uint64(resp.EntryValid / time.Second)
+ out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
+ out.AttrValid = uint64(resp.Attr.Valid / time.Second)
+ out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
+ resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
+ r.respond(buf)
+}
+
+// A RenameRequest is a request to rename a file.
+type RenameRequest struct {
+ Header `json:"-"`
+ NewDir NodeID
+ OldName, NewName string
+}
+
+var _ = Request(&RenameRequest{})
+
+func (r *RenameRequest) String() string {
+ return fmt.Sprintf("Rename [%s] from %q to dirnode %v %q", &r.Header, r.OldName, r.NewDir, r.NewName)
+}
+
+func (r *RenameRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+type MknodRequest struct {
+ Header `json:"-"`
+ Name string
+ Mode os.FileMode
+ Rdev uint32
+ // Umask of the request. Not supported on OS X.
+ Umask os.FileMode
+}
+
+var _ = Request(&MknodRequest{})
+
+func (r *MknodRequest) String() string {
+ return fmt.Sprintf("Mknod [%s] Name %q mode=%v umask=%v rdev=%d", &r.Header, r.Name, r.Mode, r.Umask, r.Rdev)
+}
+
+func (r *MknodRequest) Respond(resp *LookupResponse) {
+ size := entryOutSize(r.Header.Conn.proto)
+ buf := newBuffer(size)
+ out := (*entryOut)(buf.alloc(size))
+ out.Nodeid = uint64(resp.Node)
+ out.Generation = resp.Generation
+ out.EntryValid = uint64(resp.EntryValid / time.Second)
+ out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
+ out.AttrValid = uint64(resp.Attr.Valid / time.Second)
+ out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
+ resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
+ r.respond(buf)
+}
+
+type FsyncRequest struct {
+ Header `json:"-"`
+ Handle HandleID
+ // TODO bit 1 is datasync, not well documented upstream
+ Flags uint32
+ Dir bool
+}
+
+var _ = Request(&FsyncRequest{})
+
+func (r *FsyncRequest) String() string {
+ return fmt.Sprintf("Fsync [%s] Handle %v Flags %v", &r.Header, r.Handle, r.Flags)
+}
+
+func (r *FsyncRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
+
+// An InterruptRequest is a request to interrupt another pending request. The
+// response to that request should return an error status of EINTR.
+type InterruptRequest struct {
+ Header `json:"-"`
+ IntrID RequestID // ID of the request to be interrupt.
+}
+
+var _ = Request(&InterruptRequest{})
+
+func (r *InterruptRequest) Respond() {
+ // nothing to do here
+ r.noResponse()
+}
+
+func (r *InterruptRequest) String() string {
+ return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID)
+}
+
+// An ExchangeDataRequest is a request to exchange the contents of two
+// files, while leaving most metadata untouched.
+//
+// This request comes from OS X exchangedata(2) and represents its
+// specific semantics. Crucially, it is very different from Linux
+// renameat(2) RENAME_EXCHANGE.
+//
+// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
+type ExchangeDataRequest struct {
+ Header `json:"-"`
+ OldDir, NewDir NodeID
+ OldName, NewName string
+ // TODO options
+}
+
+var _ = Request(&ExchangeDataRequest{})
+
+func (r *ExchangeDataRequest) String() string {
+ // TODO options
+ return fmt.Sprintf("ExchangeData [%s] %v %q and %v %q", &r.Header, r.OldDir, r.OldName, r.NewDir, r.NewName)
+}
+
+func (r *ExchangeDataRequest) Respond() {
+ buf := newBuffer(0)
+ r.respond(buf)
+}
diff --git a/vendor/bazil.org/fuse/fuse.iml b/vendor/bazil.org/fuse/fuse.iml
new file mode 100644
index 000000000..792ad4c30
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="GO_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module> \ No newline at end of file
diff --git a/vendor/bazil.org/fuse/fuse_darwin.go b/vendor/bazil.org/fuse/fuse_darwin.go
new file mode 100644
index 000000000..b58dca97d
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse_darwin.go
@@ -0,0 +1,9 @@
+package fuse
+
+// Maximum file write size we are prepared to receive from the kernel.
+//
+// This value has to be >=16MB or OSXFUSE (3.4.0 observed) will
+// forcibly close the /dev/fuse file descriptor on a Setxattr with a
+// 16MB value. See TestSetxattr16MB and
+// https://github.com/bazil/fuse/issues/42
+const maxWrite = 16 * 1024 * 1024
diff --git a/vendor/bazil.org/fuse/fuse_freebsd.go b/vendor/bazil.org/fuse/fuse_freebsd.go
new file mode 100644
index 000000000..4aa83a0d4
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse_freebsd.go
@@ -0,0 +1,6 @@
+package fuse
+
+// Maximum file write size we are prepared to receive from the kernel.
+//
+// This number is just a guess.
+const maxWrite = 128 * 1024
diff --git a/vendor/bazil.org/fuse/fuse_kernel.go b/vendor/bazil.org/fuse/fuse_kernel.go
new file mode 100644
index 000000000..87c5ca1dc
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse_kernel.go
@@ -0,0 +1,774 @@
+// See the file LICENSE for copyright and licensing information.
+
+// Derived from FUSE's fuse_kernel.h, which carries this notice:
+/*
+ This file defines the kernel interface of FUSE
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
+ This -- and only this -- header file may also be distributed under
+ the terms of the BSD Licence as follows:
+
+ Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+*/
+
+package fuse
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+// The FUSE version implemented by the package.
+const (
+ protoVersionMinMajor = 7
+ protoVersionMinMinor = 8
+ protoVersionMaxMajor = 7
+ protoVersionMaxMinor = 12
+)
+
+const (
+ rootID = 1
+)
+
+type kstatfs struct {
+ Blocks uint64
+ Bfree uint64
+ Bavail uint64
+ Files uint64
+ Ffree uint64
+ Bsize uint32
+ Namelen uint32
+ Frsize uint32
+ _ uint32
+ Spare [6]uint32
+}
+
+type fileLock struct {
+ Start uint64
+ End uint64
+ Type uint32
+ Pid uint32
+}
+
+// GetattrFlags are bit flags that can be seen in GetattrRequest.
+type GetattrFlags uint32
+
+const (
+ // Indicates the handle is valid.
+ GetattrFh GetattrFlags = 1 << 0
+)
+
+var getattrFlagsNames = []flagName{
+ {uint32(GetattrFh), "GetattrFh"},
+}
+
+func (fl GetattrFlags) String() string {
+ return flagString(uint32(fl), getattrFlagsNames)
+}
+
+// The SetattrValid are bit flags describing which fields in the SetattrRequest
+// are included in the change.
+type SetattrValid uint32
+
+const (
+ SetattrMode SetattrValid = 1 << 0
+ SetattrUid SetattrValid = 1 << 1
+ SetattrGid SetattrValid = 1 << 2
+ SetattrSize SetattrValid = 1 << 3
+ SetattrAtime SetattrValid = 1 << 4
+ SetattrMtime SetattrValid = 1 << 5
+ SetattrHandle SetattrValid = 1 << 6
+
+ // Linux only(?)
+ SetattrAtimeNow SetattrValid = 1 << 7
+ SetattrMtimeNow SetattrValid = 1 << 8
+ SetattrLockOwner SetattrValid = 1 << 9 // http://www.mail-archive.com/git-commits-head@vger.kernel.org/msg27852.html
+
+ // OS X only
+ SetattrCrtime SetattrValid = 1 << 28
+ SetattrChgtime SetattrValid = 1 << 29
+ SetattrBkuptime SetattrValid = 1 << 30
+ SetattrFlags SetattrValid = 1 << 31
+)
+
+func (fl SetattrValid) Mode() bool { return fl&SetattrMode != 0 }
+func (fl SetattrValid) Uid() bool { return fl&SetattrUid != 0 }
+func (fl SetattrValid) Gid() bool { return fl&SetattrGid != 0 }
+func (fl SetattrValid) Size() bool { return fl&SetattrSize != 0 }
+func (fl SetattrValid) Atime() bool { return fl&SetattrAtime != 0 }
+func (fl SetattrValid) Mtime() bool { return fl&SetattrMtime != 0 }
+func (fl SetattrValid) Handle() bool { return fl&SetattrHandle != 0 }
+func (fl SetattrValid) AtimeNow() bool { return fl&SetattrAtimeNow != 0 }
+func (fl SetattrValid) MtimeNow() bool { return fl&SetattrMtimeNow != 0 }
+func (fl SetattrValid) LockOwner() bool { return fl&SetattrLockOwner != 0 }
+func (fl SetattrValid) Crtime() bool { return fl&SetattrCrtime != 0 }
+func (fl SetattrValid) Chgtime() bool { return fl&SetattrChgtime != 0 }
+func (fl SetattrValid) Bkuptime() bool { return fl&SetattrBkuptime != 0 }
+func (fl SetattrValid) Flags() bool { return fl&SetattrFlags != 0 }
+
+func (fl SetattrValid) String() string {
+ return flagString(uint32(fl), setattrValidNames)
+}
+
+var setattrValidNames = []flagName{
+ {uint32(SetattrMode), "SetattrMode"},
+ {uint32(SetattrUid), "SetattrUid"},
+ {uint32(SetattrGid), "SetattrGid"},
+ {uint32(SetattrSize), "SetattrSize"},
+ {uint32(SetattrAtime), "SetattrAtime"},
+ {uint32(SetattrMtime), "SetattrMtime"},
+ {uint32(SetattrHandle), "SetattrHandle"},
+ {uint32(SetattrAtimeNow), "SetattrAtimeNow"},
+ {uint32(SetattrMtimeNow), "SetattrMtimeNow"},
+ {uint32(SetattrLockOwner), "SetattrLockOwner"},
+ {uint32(SetattrCrtime), "SetattrCrtime"},
+ {uint32(SetattrChgtime), "SetattrChgtime"},
+ {uint32(SetattrBkuptime), "SetattrBkuptime"},
+ {uint32(SetattrFlags), "SetattrFlags"},
+}
+
+// Flags that can be seen in OpenRequest.Flags.
+const (
+ // Access modes. These are not 1-bit flags, but alternatives where
+ // only one can be chosen. See the IsReadOnly etc convenience
+ // methods.
+ OpenReadOnly OpenFlags = syscall.O_RDONLY
+ OpenWriteOnly OpenFlags = syscall.O_WRONLY
+ OpenReadWrite OpenFlags = syscall.O_RDWR
+
+ // File was opened in append-only mode, all writes will go to end
+ // of file. OS X does not provide this information.
+ OpenAppend OpenFlags = syscall.O_APPEND
+ OpenCreate OpenFlags = syscall.O_CREAT
+ OpenDirectory OpenFlags = syscall.O_DIRECTORY
+ OpenExclusive OpenFlags = syscall.O_EXCL
+ OpenNonblock OpenFlags = syscall.O_NONBLOCK
+ OpenSync OpenFlags = syscall.O_SYNC
+ OpenTruncate OpenFlags = syscall.O_TRUNC
+)
+
+// OpenAccessModeMask is a bitmask that separates the access mode
+// from the other flags in OpenFlags.
+const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE
+
+// OpenFlags are the O_FOO flags passed to open/create/etc calls. For
+// example, os.O_WRONLY | os.O_APPEND.
+type OpenFlags uint32
+
+func (fl OpenFlags) String() string {
+ // O_RDONLY, O_RWONLY, O_RDWR are not flags
+ s := accModeName(fl & OpenAccessModeMask)
+ flags := uint32(fl &^ OpenAccessModeMask)
+ if flags != 0 {
+ s = s + "+" + flagString(flags, openFlagNames)
+ }
+ return s
+}
+
+// Return true if OpenReadOnly is set.
+func (fl OpenFlags) IsReadOnly() bool {
+ return fl&OpenAccessModeMask == OpenReadOnly
+}
+
+// Return true if OpenWriteOnly is set.
+func (fl OpenFlags) IsWriteOnly() bool {
+ return fl&OpenAccessModeMask == OpenWriteOnly
+}
+
+// Return true if OpenReadWrite is set.
+func (fl OpenFlags) IsReadWrite() bool {
+ return fl&OpenAccessModeMask == OpenReadWrite
+}
+
+func accModeName(flags OpenFlags) string {
+ switch flags {
+ case OpenReadOnly:
+ return "OpenReadOnly"
+ case OpenWriteOnly:
+ return "OpenWriteOnly"
+ case OpenReadWrite:
+ return "OpenReadWrite"
+ default:
+ return ""
+ }
+}
+
+var openFlagNames = []flagName{
+ {uint32(OpenAppend), "OpenAppend"},
+ {uint32(OpenCreate), "OpenCreate"},
+ {uint32(OpenDirectory), "OpenDirectory"},
+ {uint32(OpenExclusive), "OpenExclusive"},
+ {uint32(OpenNonblock), "OpenNonblock"},
+ {uint32(OpenSync), "OpenSync"},
+ {uint32(OpenTruncate), "OpenTruncate"},
+}
+
+// The OpenResponseFlags are returned in the OpenResponse.
+type OpenResponseFlags uint32
+
+const (
+ OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file
+ OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open
+ OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X)
+
+ OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X
+ OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X
+)
+
+func (fl OpenResponseFlags) String() string {
+ return flagString(uint32(fl), openResponseFlagNames)
+}
+
+var openResponseFlagNames = []flagName{
+ {uint32(OpenDirectIO), "OpenDirectIO"},
+ {uint32(OpenKeepCache), "OpenKeepCache"},
+ {uint32(OpenNonSeekable), "OpenNonSeekable"},
+ {uint32(OpenPurgeAttr), "OpenPurgeAttr"},
+ {uint32(OpenPurgeUBC), "OpenPurgeUBC"},
+}
+
+// The InitFlags are used in the Init exchange.
+type InitFlags uint32
+
+const (
+ InitAsyncRead InitFlags = 1 << 0
+ InitPosixLocks InitFlags = 1 << 1
+ InitFileOps InitFlags = 1 << 2
+ InitAtomicTrunc InitFlags = 1 << 3
+ InitExportSupport InitFlags = 1 << 4
+ InitBigWrites InitFlags = 1 << 5
+ // Do not mask file access modes with umask. Not supported on OS X.
+ InitDontMask InitFlags = 1 << 6
+ InitSpliceWrite InitFlags = 1 << 7
+ InitSpliceMove InitFlags = 1 << 8
+ InitSpliceRead InitFlags = 1 << 9
+ InitFlockLocks InitFlags = 1 << 10
+ InitHasIoctlDir InitFlags = 1 << 11
+ InitAutoInvalData InitFlags = 1 << 12
+ InitDoReaddirplus InitFlags = 1 << 13
+ InitReaddirplusAuto InitFlags = 1 << 14
+ InitAsyncDIO InitFlags = 1 << 15
+ InitWritebackCache InitFlags = 1 << 16
+ InitNoOpenSupport InitFlags = 1 << 17
+
+ InitCaseSensitive InitFlags = 1 << 29 // OS X only
+ InitVolRename InitFlags = 1 << 30 // OS X only
+ InitXtimes InitFlags = 1 << 31 // OS X only
+)
+
+type flagName struct {
+ bit uint32
+ name string
+}
+
+var initFlagNames = []flagName{
+ {uint32(InitAsyncRead), "InitAsyncRead"},
+ {uint32(InitPosixLocks), "InitPosixLocks"},
+ {uint32(InitFileOps), "InitFileOps"},
+ {uint32(InitAtomicTrunc), "InitAtomicTrunc"},
+ {uint32(InitExportSupport), "InitExportSupport"},
+ {uint32(InitBigWrites), "InitBigWrites"},
+ {uint32(InitDontMask), "InitDontMask"},
+ {uint32(InitSpliceWrite), "InitSpliceWrite"},
+ {uint32(InitSpliceMove), "InitSpliceMove"},
+ {uint32(InitSpliceRead), "InitSpliceRead"},
+ {uint32(InitFlockLocks), "InitFlockLocks"},
+ {uint32(InitHasIoctlDir), "InitHasIoctlDir"},
+ {uint32(InitAutoInvalData), "InitAutoInvalData"},
+ {uint32(InitDoReaddirplus), "InitDoReaddirplus"},
+ {uint32(InitReaddirplusAuto), "InitReaddirplusAuto"},
+ {uint32(InitAsyncDIO), "InitAsyncDIO"},
+ {uint32(InitWritebackCache), "InitWritebackCache"},
+ {uint32(InitNoOpenSupport), "InitNoOpenSupport"},
+
+ {uint32(InitCaseSensitive), "InitCaseSensitive"},
+ {uint32(InitVolRename), "InitVolRename"},
+ {uint32(InitXtimes), "InitXtimes"},
+}
+
+func (fl InitFlags) String() string {
+ return flagString(uint32(fl), initFlagNames)
+}
+
+func flagString(f uint32, names []flagName) string {
+ var s string
+
+ if f == 0 {
+ return "0"
+ }
+
+ for _, n := range names {
+ if f&n.bit != 0 {
+ s += "+" + n.name
+ f &^= n.bit
+ }
+ }
+ if f != 0 {
+ s += fmt.Sprintf("%+#x", f)
+ }
+ return s[1:]
+}
+
+// The ReleaseFlags are used in the Release exchange.
+type ReleaseFlags uint32
+
+const (
+ ReleaseFlush ReleaseFlags = 1 << 0
+)
+
+func (fl ReleaseFlags) String() string {
+ return flagString(uint32(fl), releaseFlagNames)
+}
+
+var releaseFlagNames = []flagName{
+ {uint32(ReleaseFlush), "ReleaseFlush"},
+}
+
+// Opcodes
+const (
+ opLookup = 1
+ opForget = 2 // no reply
+ opGetattr = 3
+ opSetattr = 4
+ opReadlink = 5
+ opSymlink = 6
+ opMknod = 8
+ opMkdir = 9
+ opUnlink = 10
+ opRmdir = 11
+ opRename = 12
+ opLink = 13
+ opOpen = 14
+ opRead = 15
+ opWrite = 16
+ opStatfs = 17
+ opRelease = 18
+ opFsync = 20
+ opSetxattr = 21
+ opGetxattr = 22
+ opListxattr = 23
+ opRemovexattr = 24
+ opFlush = 25
+ opInit = 26
+ opOpendir = 27
+ opReaddir = 28
+ opReleasedir = 29
+ opFsyncdir = 30
+ opGetlk = 31
+ opSetlk = 32
+ opSetlkw = 33
+ opAccess = 34
+ opCreate = 35
+ opInterrupt = 36
+ opBmap = 37
+ opDestroy = 38
+ opIoctl = 39 // Linux?
+ opPoll = 40 // Linux?
+
+ // OS X
+ opSetvolname = 61
+ opGetxtimes = 62
+ opExchange = 63
+)
+
+type entryOut struct {
+ Nodeid uint64 // Inode ID
+ Generation uint64 // Inode generation
+ EntryValid uint64 // Cache timeout for the name
+ AttrValid uint64 // Cache timeout for the attributes
+ EntryValidNsec uint32
+ AttrValidNsec uint32
+ Attr attr
+}
+
+func entryOutSize(p Protocol) uintptr {
+ switch {
+ case p.LT(Protocol{7, 9}):
+ return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize)
+ default:
+ return unsafe.Sizeof(entryOut{})
+ }
+}
+
+type forgetIn struct {
+ Nlookup uint64
+}
+
+type getattrIn struct {
+ GetattrFlags uint32
+ _ uint32
+ Fh uint64
+}
+
+type attrOut struct {
+ AttrValid uint64 // Cache timeout for the attributes
+ AttrValidNsec uint32
+ _ uint32
+ Attr attr
+}
+
+func attrOutSize(p Protocol) uintptr {
+ switch {
+ case p.LT(Protocol{7, 9}):
+ return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize)
+ default:
+ return unsafe.Sizeof(attrOut{})
+ }
+}
+
+// OS X
+type getxtimesOut struct {
+ Bkuptime uint64
+ Crtime uint64
+ BkuptimeNsec uint32
+ CrtimeNsec uint32
+}
+
+type mknodIn struct {
+ Mode uint32
+ Rdev uint32
+ Umask uint32
+ _ uint32
+ // "filename\x00" follows.
+}
+
+func mknodInSize(p Protocol) uintptr {
+ switch {
+ case p.LT(Protocol{7, 12}):
+ return unsafe.Offsetof(mknodIn{}.Umask)
+ default:
+ return unsafe.Sizeof(mknodIn{})
+ }
+}
+
+type mkdirIn struct {
+ Mode uint32
+ Umask uint32
+ // filename follows
+}
+
+func mkdirInSize(p Protocol) uintptr {
+ switch {
+ case p.LT(Protocol{7, 12}):
+ return unsafe.Offsetof(mkdirIn{}.Umask) + 4
+ default:
+ return unsafe.Sizeof(mkdirIn{})
+ }
+}
+
+type renameIn struct {
+ Newdir uint64
+ // "oldname\x00newname\x00" follows
+}
+
+// OS X
+type exchangeIn struct {
+ Olddir uint64
+ Newdir uint64
+ Options uint64
+ // "oldname\x00newname\x00" follows
+}
+
+type linkIn struct {
+ Oldnodeid uint64
+}
+
+type setattrInCommon struct {
+ Valid uint32
+ _ uint32
+ Fh uint64
+ Size uint64
+ LockOwner uint64 // unused on OS X?
+ Atime uint64
+ Mtime uint64
+ Unused2 uint64
+ AtimeNsec uint32
+ MtimeNsec uint32
+ Unused3 uint32
+ Mode uint32
+ Unused4 uint32
+ Uid uint32
+ Gid uint32
+ Unused5 uint32
+}
+
+type openIn struct {
+ Flags uint32
+ Unused uint32
+}
+
+type openOut struct {
+ Fh uint64
+ OpenFlags uint32
+ _ uint32
+}
+
+type createIn struct {
+ Flags uint32
+ Mode uint32
+ Umask uint32
+ _ uint32
+}
+
+func createInSize(p Protocol) uintptr {
+ switch {
+ case p.LT(Protocol{7, 12}):
+ return unsafe.Offsetof(createIn{}.Umask)
+ default:
+ return unsafe.Sizeof(createIn{})
+ }
+}
+
+type releaseIn struct {
+ Fh uint64
+ Flags uint32
+ ReleaseFlags uint32
+ LockOwner uint32
+}
+
+type flushIn struct {
+ Fh uint64
+ FlushFlags uint32
+ _ uint32
+ LockOwner uint64
+}
+
+type readIn struct {
+ Fh uint64
+ Offset uint64
+ Size uint32
+ ReadFlags uint32
+ LockOwner uint64
+ Flags uint32
+ _ uint32
+}
+
+func readInSize(p Protocol) uintptr {
+ switch {
+ case p.LT(Protocol{7, 9}):
+ return unsafe.Offsetof(readIn{}.ReadFlags) + 4
+ default:
+ return unsafe.Sizeof(readIn{})
+ }
+}
+
+// The ReadFlags are passed in ReadRequest.
+type ReadFlags uint32
+
+const (
+ // LockOwner field is valid.
+ ReadLockOwner ReadFlags = 1 << 1
+)
+
+var readFlagNames = []flagName{
+ {uint32(ReadLockOwner), "ReadLockOwner"},
+}
+
+func (fl ReadFlags) String() string {
+ return flagString(uint32(fl), readFlagNames)
+}
+
+type writeIn struct {
+ Fh uint64
+ Offset uint64
+ Size uint32
+ WriteFlags uint32
+ LockOwner uint64
+ Flags uint32
+ _ uint32
+}
+
+func writeInSize(p Protocol) uintptr {
+ switch {
+ case p.LT(Protocol{7, 9}):
+ return unsafe.Offsetof(writeIn{}.LockOwner)
+ default:
+ return unsafe.Sizeof(writeIn{})
+ }
+}
+
+type writeOut struct {
+ Size uint32
+ _ uint32
+}
+
+// The WriteFlags are passed in WriteRequest.
+type WriteFlags uint32
+
+const (
+ WriteCache WriteFlags = 1 << 0
+ // LockOwner field is valid.
+ WriteLockOwner WriteFlags = 1 << 1
+)
+
+var writeFlagNames = []flagName{
+ {uint32(WriteCache), "WriteCache"},
+ {uint32(WriteLockOwner), "WriteLockOwner"},
+}
+
+func (fl WriteFlags) String() string {
+ return flagString(uint32(fl), writeFlagNames)
+}
+
+const compatStatfsSize = 48
+
+type statfsOut struct {
+ St kstatfs
+}
+
+type fsyncIn struct {
+ Fh uint64
+ FsyncFlags uint32
+ _ uint32
+}
+
+type setxattrInCommon struct {
+ Size uint32
+ Flags uint32
+}
+
+func (setxattrInCommon) position() uint32 {
+ return 0
+}
+
+type getxattrInCommon struct {
+ Size uint32
+ _ uint32
+}
+
+func (getxattrInCommon) position() uint32 {
+ return 0
+}
+
+type getxattrOut struct {
+ Size uint32
+ _ uint32
+}
+
+type lkIn struct {
+ Fh uint64
+ Owner uint64
+ Lk fileLock
+ LkFlags uint32
+ _ uint32
+}
+
+func lkInSize(p Protocol) uintptr {
+ switch {
+ case p.LT(Protocol{7, 9}):
+ return unsafe.Offsetof(lkIn{}.LkFlags)
+ default:
+ return unsafe.Sizeof(lkIn{})
+ }
+}
+
+type lkOut struct {
+ Lk fileLock
+}
+
+type accessIn struct {
+ Mask uint32
+ _ uint32
+}
+
+type initIn struct {
+ Major uint32
+ Minor uint32
+ MaxReadahead uint32
+ Flags uint32
+}
+
+const initInSize = int(unsafe.Sizeof(initIn{}))
+
+type initOut struct {
+ Major uint32
+ Minor uint32
+ MaxReadahead uint32
+ Flags uint32
+ Unused uint32
+ MaxWrite uint32
+}
+
+type interruptIn struct {
+ Unique uint64
+}
+
+type bmapIn struct {
+ Block uint64
+ BlockSize uint32
+ _ uint32
+}
+
+type bmapOut struct {
+ Block uint64
+}
+
+type inHeader struct {
+ Len uint32
+ Opcode uint32
+ Unique uint64
+ Nodeid uint64
+ Uid uint32
+ Gid uint32
+ Pid uint32
+ _ uint32
+}
+
+const inHeaderSize = int(unsafe.Sizeof(inHeader{}))
+
+type outHeader struct {
+ Len uint32
+ Error int32
+ Unique uint64
+}
+
+type dirent struct {
+ Ino uint64
+ Off uint64
+ Namelen uint32
+ Type uint32
+ Name [0]byte
+}
+
+const direntSize = 8 + 8 + 4 + 4
+
+const (
+ notifyCodePoll int32 = 1
+ notifyCodeInvalInode int32 = 2
+ notifyCodeInvalEntry int32 = 3
+)
+
+type notifyInvalInodeOut struct {
+ Ino uint64
+ Off int64
+ Len int64
+}
+
+type notifyInvalEntryOut struct {
+ Parent uint64
+ Namelen uint32
+ _ uint32
+}
diff --git a/vendor/bazil.org/fuse/fuse_kernel_darwin.go b/vendor/bazil.org/fuse/fuse_kernel_darwin.go
new file mode 100644
index 000000000..b9873fdf3
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse_kernel_darwin.go
@@ -0,0 +1,88 @@
+package fuse
+
+import (
+ "time"
+)
+
+type attr struct {
+ Ino uint64
+ Size uint64
+ Blocks uint64
+ Atime uint64
+ Mtime uint64
+ Ctime uint64
+ Crtime_ uint64 // OS X only
+ AtimeNsec uint32
+ MtimeNsec uint32
+ CtimeNsec uint32
+ CrtimeNsec uint32 // OS X only
+ Mode uint32
+ Nlink uint32
+ Uid uint32
+ Gid uint32
+ Rdev uint32
+ Flags_ uint32 // OS X only; see chflags(2)
+ Blksize uint32
+ padding uint32
+}
+
+func (a *attr) SetCrtime(s uint64, ns uint32) {
+ a.Crtime_, a.CrtimeNsec = s, ns
+}
+
+func (a *attr) SetFlags(f uint32) {
+ a.Flags_ = f
+}
+
+type setattrIn struct {
+ setattrInCommon
+
+ // OS X only
+ Bkuptime_ uint64
+ Chgtime_ uint64
+ Crtime uint64
+ BkuptimeNsec uint32
+ ChgtimeNsec uint32
+ CrtimeNsec uint32
+ Flags_ uint32 // see chflags(2)
+}
+
+func (in *setattrIn) BkupTime() time.Time {
+ return time.Unix(int64(in.Bkuptime_), int64(in.BkuptimeNsec))
+}
+
+func (in *setattrIn) Chgtime() time.Time {
+ return time.Unix(int64(in.Chgtime_), int64(in.ChgtimeNsec))
+}
+
+func (in *setattrIn) Flags() uint32 {
+ return in.Flags_
+}
+
+func openFlags(flags uint32) OpenFlags {
+ return OpenFlags(flags)
+}
+
+type getxattrIn struct {
+ getxattrInCommon
+
+ // OS X only
+ Position uint32
+ Padding uint32
+}
+
+func (g *getxattrIn) position() uint32 {
+ return g.Position
+}
+
+type setxattrIn struct {
+ setxattrInCommon
+
+ // OS X only
+ Position uint32
+ Padding uint32
+}
+
+func (s *setxattrIn) position() uint32 {
+ return s.Position
+}
diff --git a/vendor/bazil.org/fuse/fuse_kernel_freebsd.go b/vendor/bazil.org/fuse/fuse_kernel_freebsd.go
new file mode 100644
index 000000000..b1141e41d
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse_kernel_freebsd.go
@@ -0,0 +1,62 @@
+package fuse
+
+import "time"
+
+type attr struct {
+ Ino uint64
+ Size uint64
+ Blocks uint64
+ Atime uint64
+ Mtime uint64
+ Ctime uint64
+ AtimeNsec uint32
+ MtimeNsec uint32
+ CtimeNsec uint32
+ Mode uint32
+ Nlink uint32
+ Uid uint32
+ Gid uint32
+ Rdev uint32
+ Blksize uint32
+ padding uint32
+}
+
+func (a *attr) Crtime() time.Time {
+ return time.Time{}
+}
+
+func (a *attr) SetCrtime(s uint64, ns uint32) {
+ // ignored on freebsd
+}
+
+func (a *attr) SetFlags(f uint32) {
+ // ignored on freebsd
+}
+
+type setattrIn struct {
+ setattrInCommon
+}
+
+func (in *setattrIn) BkupTime() time.Time {
+ return time.Time{}
+}
+
+func (in *setattrIn) Chgtime() time.Time {
+ return time.Time{}
+}
+
+func (in *setattrIn) Flags() uint32 {
+ return 0
+}
+
+func openFlags(flags uint32) OpenFlags {
+ return OpenFlags(flags)
+}
+
+type getxattrIn struct {
+ getxattrInCommon
+}
+
+type setxattrIn struct {
+ setxattrInCommon
+}
diff --git a/vendor/bazil.org/fuse/fuse_kernel_linux.go b/vendor/bazil.org/fuse/fuse_kernel_linux.go
new file mode 100644
index 000000000..d3ba86617
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse_kernel_linux.go
@@ -0,0 +1,70 @@
+package fuse
+
+import "time"
+
+type attr struct {
+ Ino uint64
+ Size uint64
+ Blocks uint64
+ Atime uint64
+ Mtime uint64
+ Ctime uint64
+ AtimeNsec uint32
+ MtimeNsec uint32
+ CtimeNsec uint32
+ Mode uint32
+ Nlink uint32
+ Uid uint32
+ Gid uint32
+ Rdev uint32
+ Blksize uint32
+ padding uint32
+}
+
+func (a *attr) Crtime() time.Time {
+ return time.Time{}
+}
+
+func (a *attr) SetCrtime(s uint64, ns uint32) {
+ // Ignored on Linux.
+}
+
+func (a *attr) SetFlags(f uint32) {
+ // Ignored on Linux.
+}
+
+type setattrIn struct {
+ setattrInCommon
+}
+
+func (in *setattrIn) BkupTime() time.Time {
+ return time.Time{}
+}
+
+func (in *setattrIn) Chgtime() time.Time {
+ return time.Time{}
+}
+
+func (in *setattrIn) Flags() uint32 {
+ return 0
+}
+
+func openFlags(flags uint32) OpenFlags {
+ // on amd64, the 32-bit O_LARGEFILE flag is always seen;
+ // on i386, the flag probably depends on the app
+ // requesting, but in any case should be utterly
+ // uninteresting to us here; our kernel protocol messages
+ // are not directly related to the client app's kernel
+ // API/ABI
+ flags &^= 0x8000
+
+ return OpenFlags(flags)
+}
+
+type getxattrIn struct {
+ getxattrInCommon
+}
+
+type setxattrIn struct {
+ setxattrInCommon
+}
diff --git a/vendor/bazil.org/fuse/fuse_kernel_std.go b/vendor/bazil.org/fuse/fuse_kernel_std.go
new file mode 100644
index 000000000..074cfd322
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse_kernel_std.go
@@ -0,0 +1 @@
+package fuse
diff --git a/vendor/bazil.org/fuse/fuse_linux.go b/vendor/bazil.org/fuse/fuse_linux.go
new file mode 100644
index 000000000..5fb96f9ae
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuse_linux.go
@@ -0,0 +1,7 @@
+package fuse
+
+// Maximum file write size we are prepared to receive from the kernel.
+//
+// Linux 4.2.0 has been observed to cap this value at 128kB
+// (FUSE_MAX_PAGES_PER_REQ=32, 4kB pages).
+const maxWrite = 128 * 1024
diff --git a/vendor/bazil.org/fuse/fuseutil/fuseutil.go b/vendor/bazil.org/fuse/fuseutil/fuseutil.go
new file mode 100644
index 000000000..b3f52b73b
--- /dev/null
+++ b/vendor/bazil.org/fuse/fuseutil/fuseutil.go
@@ -0,0 +1,20 @@
+package fuseutil // import "bazil.org/fuse/fuseutil"
+
+import (
+ "bazil.org/fuse"
+)
+
+// HandleRead handles a read request assuming that data is the entire file content.
+// It adjusts the amount returned in resp according to req.Offset and req.Size.
+func HandleRead(req *fuse.ReadRequest, resp *fuse.ReadResponse, data []byte) {
+ if req.Offset >= int64(len(data)) {
+ data = nil
+ } else {
+ data = data[req.Offset:]
+ }
+ if len(data) > req.Size {
+ data = data[:req.Size]
+ }
+ n := copy(resp.Data[:req.Size], data)
+ resp.Data = resp.Data[:n]
+}
diff --git a/vendor/bazil.org/fuse/mount.go b/vendor/bazil.org/fuse/mount.go
new file mode 100644
index 000000000..8054e9021
--- /dev/null
+++ b/vendor/bazil.org/fuse/mount.go
@@ -0,0 +1,38 @@
+package fuse
+
+import (
+ "bufio"
+ "errors"
+ "io"
+ "log"
+ "sync"
+)
+
+var (
+ // ErrOSXFUSENotFound is returned from Mount when the OSXFUSE
+ // installation is not detected.
+ //
+ // Only happens on OS X. Make sure OSXFUSE is installed, or see
+ // OSXFUSELocations for customization.
+ ErrOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
+)
+
+func neverIgnoreLine(line string) bool {
+ return false
+}
+
+func lineLogger(wg *sync.WaitGroup, prefix string, ignore func(line string) bool, r io.ReadCloser) {
+ defer wg.Done()
+
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if ignore(line) {
+ continue
+ }
+ log.Printf("%s: %s", prefix, line)
+ }
+ if err := scanner.Err(); err != nil {
+ log.Printf("%s, error reading: %v", prefix, err)
+ }
+}
diff --git a/vendor/bazil.org/fuse/mount_darwin.go b/vendor/bazil.org/fuse/mount_darwin.go
new file mode 100644
index 000000000..c1c36e62b
--- /dev/null
+++ b/vendor/bazil.org/fuse/mount_darwin.go
@@ -0,0 +1,208 @@
+package fuse
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+)
+
+var (
+ errNoAvail = errors.New("no available fuse devices")
+ errNotLoaded = errors.New("osxfuse is not loaded")
+)
+
+func loadOSXFUSE(bin string) error {
+ cmd := exec.Command(bin)
+ cmd.Dir = "/"
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err := cmd.Run()
+ return err
+}
+
+func openOSXFUSEDev(devPrefix string) (*os.File, error) {
+ var f *os.File
+ var err error
+ for i := uint64(0); ; i++ {
+ path := devPrefix + strconv.FormatUint(i, 10)
+ f, err = os.OpenFile(path, os.O_RDWR, 0000)
+ if os.IsNotExist(err) {
+ if i == 0 {
+ // not even the first device was found -> fuse is not loaded
+ return nil, errNotLoaded
+ }
+
+ // we've run out of kernel-provided devices
+ return nil, errNoAvail
+ }
+
+ if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY {
+ // try the next one
+ continue
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ return f, nil
+ }
+}
+
+func handleMountOSXFUSE(helperName string, errCh chan<- error) func(line string) (ignore bool) {
+ var noMountpointPrefix = helperName + `: `
+ const noMountpointSuffix = `: No such file or directory`
+ return func(line string) (ignore bool) {
+ if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
+ // re-extract it from the error message in case some layer
+ // changed the path
+ mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
+ err := &MountpointDoesNotExistError{
+ Path: mountpoint,
+ }
+ select {
+ case errCh <- err:
+ return true
+ default:
+ // not the first error; fall back to logging it
+ return false
+ }
+ }
+
+ return false
+ }
+}
+
+// isBoringMountOSXFUSEError returns whether the Wait error is
+// uninteresting; exit status 64 is.
+func isBoringMountOSXFUSEError(err error) bool {
+ if err, ok := err.(*exec.ExitError); ok && err.Exited() {
+ if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 64 {
+ return true
+ }
+ }
+ return false
+}
+
+func callMount(bin string, daemonVar string, dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
+ for k, v := range conf.options {
+ if strings.Contains(k, ",") || strings.Contains(v, ",") {
+ // Silly limitation but the mount helper does not
+ // understand any escaping. See TestMountOptionCommaError.
+ return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v)
+ }
+ }
+ cmd := exec.Command(
+ bin,
+ "-o", conf.getOptions(),
+ // Tell osxfuse-kext how large our buffer is. It must split
+ // writes larger than this into multiple writes.
+ //
+ // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
+ // this instead.
+ "-o", "iosize="+strconv.FormatUint(maxWrite, 10),
+ // refers to fd passed in cmd.ExtraFiles
+ "3",
+ dir,
+ )
+ cmd.ExtraFiles = []*os.File{f}
+ cmd.Env = os.Environ()
+ // OSXFUSE <3.3.0
+ cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
+ // OSXFUSE >=3.3.0
+ cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=")
+
+ daemon := os.Args[0]
+ if daemonVar != "" {
+ cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
+ }
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
+ }
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
+ }
+
+ if err := cmd.Start(); err != nil {
+ return fmt.Errorf("mount_osxfusefs: %v", err)
+ }
+ helperErrCh := make(chan error, 1)
+ go func() {
+ var wg sync.WaitGroup
+ wg.Add(2)
+ go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
+ helperName := path.Base(bin)
+ go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, helperErrCh), stderr)
+ wg.Wait()
+ if err := cmd.Wait(); err != nil {
+ // see if we have a better error to report
+ select {
+ case helperErr := <-helperErrCh:
+ // log the Wait error if it's not what we expected
+ if !isBoringMountOSXFUSEError(err) {
+ log.Printf("mount helper failed: %v", err)
+ }
+ // and now return what we grabbed from stderr as the real
+ // error
+ *errp = helperErr
+ close(ready)
+ return
+ default:
+ // nope, fall back to generic message
+ }
+
+ *errp = fmt.Errorf("mount_osxfusefs: %v", err)
+ close(ready)
+ return
+ }
+
+ *errp = nil
+ close(ready)
+ }()
+ return nil
+}
+
+func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
+ locations := conf.osxfuseLocations
+ if locations == nil {
+ locations = []OSXFUSEPaths{
+ OSXFUSELocationV3,
+ OSXFUSELocationV2,
+ }
+ }
+ for _, loc := range locations {
+ if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
+ // try the other locations
+ continue
+ }
+
+ f, err := openOSXFUSEDev(loc.DevicePrefix)
+ if err == errNotLoaded {
+ err = loadOSXFUSE(loc.Load)
+ if err != nil {
+ return nil, err
+ }
+ // try again
+ f, err = openOSXFUSEDev(loc.DevicePrefix)
+ }
+ if err != nil {
+ return nil, err
+ }
+ err = callMount(loc.Mount, loc.DaemonVar, dir, conf, f, ready, errp)
+ if err != nil {
+ f.Close()
+ return nil, err
+ }
+ return f, nil
+ }
+ return nil, ErrOSXFUSENotFound
+}
diff --git a/vendor/bazil.org/fuse/mount_freebsd.go b/vendor/bazil.org/fuse/mount_freebsd.go
new file mode 100644
index 000000000..70bb41024
--- /dev/null
+++ b/vendor/bazil.org/fuse/mount_freebsd.go
@@ -0,0 +1,111 @@
+package fuse
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+ "syscall"
+)
+
+func handleMountFusefsStderr(errCh chan<- error) func(line string) (ignore bool) {
+ return func(line string) (ignore bool) {
+ const (
+ noMountpointPrefix = `mount_fusefs: `
+ noMountpointSuffix = `: No such file or directory`
+ )
+ if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
+ // re-extract it from the error message in case some layer
+ // changed the path
+ mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
+ err := &MountpointDoesNotExistError{
+ Path: mountpoint,
+ }
+ select {
+ case errCh <- err:
+ return true
+ default:
+ // not the first error; fall back to logging it
+ return false
+ }
+ }
+
+ return false
+ }
+}
+
+// isBoringMountFusefsError returns whether the Wait error is
+// uninteresting; exit status 1 is.
+func isBoringMountFusefsError(err error) bool {
+ if err, ok := err.(*exec.ExitError); ok && err.Exited() {
+ if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 {
+ return true
+ }
+ }
+ return false
+}
+
+func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
+ for k, v := range conf.options {
+ if strings.Contains(k, ",") || strings.Contains(v, ",") {
+ // Silly limitation but the mount helper does not
+ // understand any escaping. See TestMountOptionCommaError.
+ return nil, fmt.Errorf("mount options cannot contain commas on FreeBSD: %q=%q", k, v)
+ }
+ }
+
+ f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0000)
+ if err != nil {
+ *errp = err
+ return nil, err
+ }
+
+ cmd := exec.Command(
+ "/sbin/mount_fusefs",
+ "--safe",
+ "-o", conf.getOptions(),
+ "3",
+ dir,
+ )
+ cmd.ExtraFiles = []*os.File{f}
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err)
+ }
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err)
+ }
+
+ if err := cmd.Start(); err != nil {
+ return nil, fmt.Errorf("mount_fusefs: %v", err)
+ }
+ helperErrCh := make(chan error, 1)
+ var wg sync.WaitGroup
+ wg.Add(2)
+ go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
+ go lineLogger(&wg, "mount helper error", handleMountFusefsStderr(helperErrCh), stderr)
+ wg.Wait()
+ if err := cmd.Wait(); err != nil {
+ // see if we have a better error to report
+ select {
+ case helperErr := <-helperErrCh:
+ // log the Wait error if it's not what we expected
+ if !isBoringMountFusefsError(err) {
+ log.Printf("mount helper failed: %v", err)
+ }
+ // and now return what we grabbed from stderr as the real
+ // error
+ return nil, helperErr
+ default:
+ // nope, fall back to generic message
+ }
+ return nil, fmt.Errorf("mount_fusefs: %v", err)
+ }
+
+ close(ready)
+ return f, nil
+}
diff --git a/vendor/bazil.org/fuse/mount_linux.go b/vendor/bazil.org/fuse/mount_linux.go
new file mode 100644
index 000000000..197d1044e
--- /dev/null
+++ b/vendor/bazil.org/fuse/mount_linux.go
@@ -0,0 +1,150 @@
+package fuse
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+ "syscall"
+)
+
+func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) {
+ return func(line string) (ignore bool) {
+ if line == `fusermount: failed to open /etc/fuse.conf: Permission denied` {
+ // Silence this particular message, it occurs way too
+ // commonly and isn't very relevant to whether the mount
+ // succeeds or not.
+ return true
+ }
+
+ const (
+ noMountpointPrefix = `fusermount: failed to access mountpoint `
+ noMountpointSuffix = `: No such file or directory`
+ )
+ if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
+ // re-extract it from the error message in case some layer
+ // changed the path
+ mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
+ err := &MountpointDoesNotExistError{
+ Path: mountpoint,
+ }
+ select {
+ case errCh <- err:
+ return true
+ default:
+ // not the first error; fall back to logging it
+ return false
+ }
+ }
+
+ return false
+ }
+}
+
+// isBoringFusermountError returns whether the Wait error is
+// uninteresting; exit status 1 is.
+func isBoringFusermountError(err error) bool {
+ if err, ok := err.(*exec.ExitError); ok && err.Exited() {
+ if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 {
+ return true
+ }
+ }
+ return false
+}
+
+func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) {
+ // linux mount is never delayed
+ close(ready)
+
+ fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
+ if err != nil {
+ return nil, fmt.Errorf("socketpair error: %v", err)
+ }
+
+ writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
+ defer writeFile.Close()
+
+ readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
+ defer readFile.Close()
+
+ cmd := exec.Command(
+ "fusermount",
+ "-o", conf.getOptions(),
+ "--",
+ dir,
+ )
+ cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
+
+ cmd.ExtraFiles = []*os.File{writeFile}
+
+ var wg sync.WaitGroup
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, fmt.Errorf("setting up fusermount stderr: %v", err)
+ }
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ return nil, fmt.Errorf("setting up fusermount stderr: %v", err)
+ }
+
+ if err := cmd.Start(); err != nil {
+ return nil, fmt.Errorf("fusermount: %v", err)
+ }
+ helperErrCh := make(chan error, 1)
+ wg.Add(2)
+ go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
+ go lineLogger(&wg, "mount helper error", handleFusermountStderr(helperErrCh), stderr)
+ wg.Wait()
+ if err := cmd.Wait(); err != nil {
+ // see if we have a better error to report
+ select {
+ case helperErr := <-helperErrCh:
+ // log the Wait error if it's not what we expected
+ if !isBoringFusermountError(err) {
+ log.Printf("mount helper failed: %v", err)
+ }
+ // and now return what we grabbed from stderr as the real
+ // error
+ return nil, helperErr
+ default:
+ // nope, fall back to generic message
+ }
+
+ return nil, fmt.Errorf("fusermount: %v", err)
+ }
+
+ c, err := net.FileConn(readFile)
+ if err != nil {
+ return nil, fmt.Errorf("FileConn from fusermount socket: %v", err)
+ }
+ defer c.Close()
+
+ uc, ok := c.(*net.UnixConn)
+ if !ok {
+ return nil, fmt.Errorf("unexpected FileConn type; expected UnixConn, got %T", c)
+ }
+
+ buf := make([]byte, 32) // expect 1 byte
+ oob := make([]byte, 32) // expect 24 bytes
+ _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
+ scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
+ if err != nil {
+ return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
+ }
+ if len(scms) != 1 {
+ return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
+ }
+ scm := scms[0]
+ gotFds, err := syscall.ParseUnixRights(&scm)
+ if err != nil {
+ return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
+ }
+ if len(gotFds) != 1 {
+ return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
+ }
+ f := os.NewFile(uintptr(gotFds[0]), "/dev/fuse")
+ return f, nil
+}
diff --git a/vendor/bazil.org/fuse/options.go b/vendor/bazil.org/fuse/options.go
new file mode 100644
index 000000000..65ce8a541
--- /dev/null
+++ b/vendor/bazil.org/fuse/options.go
@@ -0,0 +1,310 @@
+package fuse
+
+import (
+ "errors"
+ "strings"
+)
+
+func dummyOption(conf *mountConfig) error {
+ return nil
+}
+
+// mountConfig holds the configuration for a mount operation.
+// Use it by passing MountOption values to Mount.
+type mountConfig struct {
+ options map[string]string
+ maxReadahead uint32
+ initFlags InitFlags
+ osxfuseLocations []OSXFUSEPaths
+}
+
+func escapeComma(s string) string {
+ s = strings.Replace(s, `\`, `\\`, -1)
+ s = strings.Replace(s, `,`, `\,`, -1)
+ return s
+}
+
+// getOptions makes a string of options suitable for passing to FUSE
+// mount flag `-o`. Returns an empty string if no options were set.
+// Any platform specific adjustments should happen before the call.
+func (m *mountConfig) getOptions() string {
+ var opts []string
+ for k, v := range m.options {
+ k = escapeComma(k)
+ if v != "" {
+ k += "=" + escapeComma(v)
+ }
+ opts = append(opts, k)
+ }
+ return strings.Join(opts, ",")
+}
+
+type mountOption func(*mountConfig) error
+
+// MountOption is passed to Mount to change the behavior of the mount.
+type MountOption mountOption
+
+// FSName sets the file system name (also called source) that is
+// visible in the list of mounted file systems.
+//
+// FreeBSD ignores this option.
+func FSName(name string) MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["fsname"] = name
+ return nil
+ }
+}
+
+// Subtype sets the subtype of the mount. The main type is always
+// `fuse`. The type in a list of mounted file systems will look like
+// `fuse.foo`.
+//
+// OS X ignores this option.
+// FreeBSD ignores this option.
+func Subtype(fstype string) MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["subtype"] = fstype
+ return nil
+ }
+}
+
+// LocalVolume sets the volume to be local (instead of network),
+// changing the behavior of Finder, Spotlight, and such.
+//
+// OS X only. Others ignore this option.
+func LocalVolume() MountOption {
+ return localVolume
+}
+
+// VolumeName sets the volume name shown in Finder.
+//
+// OS X only. Others ignore this option.
+func VolumeName(name string) MountOption {
+ return volumeName(name)
+}
+
+// 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
+//
+// OS X only. Others ignore this option.
+func NoAppleDouble() MountOption {
+ return noAppleDouble
+}
+
+// NoAppleXattr makes OSXFUSE disallow extended attributes with the
+// prefix "com.apple.". This disables persistent Finder state and
+// other such information.
+//
+// OS X only. Others ignore this option.
+func NoAppleXattr() MountOption {
+ return noAppleXattr
+}
+
+// ExclCreate causes O_EXCL flag to be set for only "truly" exclusive creates,
+// i.e. create calls for which the initiator explicitly set the O_EXCL flag.
+//
+// OSXFUSE expects all create calls to return EEXIST in case the file
+// already exists, regardless of whether O_EXCL was specified or not.
+// To ensure this behavior, it normally sets OpenExclusive for all
+// Create calls, regardless of whether the original call had it set.
+// For distributed filesystems, that may force every file create to be
+// a distributed consensus action, causing undesirable delays.
+//
+// This option makes the FUSE filesystem see the original flag value,
+// and better decide when to ensure global consensus.
+//
+// Note that returning EEXIST on existing file create is still
+// expected with OSXFUSE, regardless of the presence of the
+// OpenExclusive flag.
+//
+// For more information, see
+// https://github.com/osxfuse/osxfuse/issues/209
+//
+// OS X only. Others ignore this options.
+// Requires OSXFUSE 3.4.1 or newer.
+func ExclCreate() MountOption {
+ return exclCreate
+}
+
+// DaemonTimeout sets the time in seconds between a request and a reply before
+// the FUSE mount is declared dead.
+//
+// OS X and FreeBSD only. Others ignore this option.
+func DaemonTimeout(name string) MountOption {
+ return daemonTimeout(name)
+}
+
+var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot")
+
+// AllowOther allows other users to access the file system.
+//
+// Only one of AllowOther or AllowRoot can be used.
+func AllowOther() MountOption {
+ return func(conf *mountConfig) error {
+ if _, ok := conf.options["allow_root"]; ok {
+ return ErrCannotCombineAllowOtherAndAllowRoot
+ }
+ conf.options["allow_other"] = ""
+ return nil
+ }
+}
+
+// AllowRoot allows other users to access the file system.
+//
+// Only one of AllowOther or AllowRoot can be used.
+//
+// FreeBSD ignores this option.
+func AllowRoot() MountOption {
+ return func(conf *mountConfig) error {
+ if _, ok := conf.options["allow_other"]; ok {
+ return ErrCannotCombineAllowOtherAndAllowRoot
+ }
+ conf.options["allow_root"] = ""
+ return nil
+ }
+}
+
+// AllowDev enables interpreting character or block special devices on the
+// filesystem.
+func AllowDev() MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["dev"] = ""
+ return nil
+ }
+}
+
+// AllowSUID allows set-user-identifier or set-group-identifier bits to take
+// effect.
+func AllowSUID() MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["suid"] = ""
+ return nil
+ }
+}
+
+// DefaultPermissions makes the kernel enforce access control based on
+// the file mode (as in chmod).
+//
+// Without this option, the Node itself decides what is and is not
+// allowed. This is normally ok because FUSE file systems cannot be
+// accessed by other users without AllowOther/AllowRoot.
+//
+// FreeBSD ignores this option.
+func DefaultPermissions() MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["default_permissions"] = ""
+ return nil
+ }
+}
+
+// ReadOnly makes the mount read-only.
+func ReadOnly() MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["ro"] = ""
+ return nil
+ }
+}
+
+// MaxReadahead sets the number of bytes that can be prefetched for
+// sequential reads. The kernel can enforce a maximum value lower than
+// this.
+//
+// This setting makes the kernel perform speculative reads that do not
+// originate from any client process. This usually tremendously
+// improves read performance.
+func MaxReadahead(n uint32) MountOption {
+ return func(conf *mountConfig) error {
+ conf.maxReadahead = n
+ return nil
+ }
+}
+
+// AsyncRead enables multiple outstanding read requests for the same
+// handle. Without this, there is at most one request in flight at a
+// time.
+func AsyncRead() MountOption {
+ return func(conf *mountConfig) error {
+ conf.initFlags |= InitAsyncRead
+ return nil
+ }
+}
+
+// WritebackCache enables the kernel to buffer writes before sending
+// them to the FUSE server. Without this, writethrough caching is
+// used.
+func WritebackCache() MountOption {
+ return func(conf *mountConfig) error {
+ conf.initFlags |= InitWritebackCache
+ return nil
+ }
+}
+
+// OSXFUSEPaths describes the paths used by an installed OSXFUSE
+// version. See OSXFUSELocationV3 for typical values.
+type OSXFUSEPaths struct {
+ // Prefix for the device file. At mount time, an incrementing
+ // number is suffixed until a free FUSE device is found.
+ DevicePrefix string
+ // Path of the load helper, used to load the kernel extension if
+ // no device files are found.
+ Load string
+ // Path of the mount helper, used for the actual mount operation.
+ Mount string
+ // Environment variable used to pass the path to the executable
+ // calling the mount helper.
+ DaemonVar string
+}
+
+// Default paths for OSXFUSE. See OSXFUSELocations.
+var (
+ OSXFUSELocationV3 = OSXFUSEPaths{
+ DevicePrefix: "/dev/osxfuse",
+ Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
+ Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
+ DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
+ }
+ OSXFUSELocationV2 = OSXFUSEPaths{
+ DevicePrefix: "/dev/osxfuse",
+ Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
+ Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
+ DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
+ }
+)
+
+// OSXFUSELocations sets where to look for OSXFUSE files. The
+// arguments are all the possible locations. The previous locations
+// are replaced.
+//
+// Without this option, OSXFUSELocationV3 and OSXFUSELocationV2 are
+// used.
+//
+// OS X only. Others ignore this option.
+func OSXFUSELocations(paths ...OSXFUSEPaths) MountOption {
+ return func(conf *mountConfig) error {
+ if len(paths) == 0 {
+ return errors.New("must specify at least one location for OSXFUSELocations")
+ }
+ // replace previous values, but make a copy so there's no
+ // worries about caller mutating their slice
+ conf.osxfuseLocations = append(conf.osxfuseLocations[:0], paths...)
+ return nil
+ }
+}
+
+// AllowNonEmptyMount allows the mounting over a non-empty directory.
+//
+// The files in it will be shadowed by the freshly created mount. By
+// default these mounts are rejected to prevent accidental covering up
+// of data, which could for example prevent automatic backup.
+func AllowNonEmptyMount() MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["nonempty"] = ""
+ return nil
+ }
+}
diff --git a/vendor/bazil.org/fuse/options_darwin.go b/vendor/bazil.org/fuse/options_darwin.go
new file mode 100644
index 000000000..faa9d78e7
--- /dev/null
+++ b/vendor/bazil.org/fuse/options_darwin.go
@@ -0,0 +1,35 @@
+package fuse
+
+func localVolume(conf *mountConfig) error {
+ conf.options["local"] = ""
+ return nil
+}
+
+func volumeName(name string) MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["volname"] = name
+ return nil
+ }
+}
+
+func daemonTimeout(name string) MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["daemon_timeout"] = name
+ return nil
+ }
+}
+
+func noAppleXattr(conf *mountConfig) error {
+ conf.options["noapplexattr"] = ""
+ return nil
+}
+
+func noAppleDouble(conf *mountConfig) error {
+ conf.options["noappledouble"] = ""
+ return nil
+}
+
+func exclCreate(conf *mountConfig) error {
+ conf.options["excl_create"] = ""
+ return nil
+}
diff --git a/vendor/bazil.org/fuse/options_freebsd.go b/vendor/bazil.org/fuse/options_freebsd.go
new file mode 100644
index 000000000..7c164b136
--- /dev/null
+++ b/vendor/bazil.org/fuse/options_freebsd.go
@@ -0,0 +1,28 @@
+package fuse
+
+func localVolume(conf *mountConfig) error {
+ return nil
+}
+
+func volumeName(name string) MountOption {
+ return dummyOption
+}
+
+func daemonTimeout(name string) MountOption {
+ return func(conf *mountConfig) error {
+ conf.options["timeout"] = name
+ return nil
+ }
+}
+
+func noAppleXattr(conf *mountConfig) error {
+ return nil
+}
+
+func noAppleDouble(conf *mountConfig) error {
+ return nil
+}
+
+func exclCreate(conf *mountConfig) error {
+ return nil
+}
diff --git a/vendor/bazil.org/fuse/options_linux.go b/vendor/bazil.org/fuse/options_linux.go
new file mode 100644
index 000000000..13f0896d5
--- /dev/null
+++ b/vendor/bazil.org/fuse/options_linux.go
@@ -0,0 +1,25 @@
+package fuse
+
+func localVolume(conf *mountConfig) error {
+ return nil
+}
+
+func volumeName(name string) MountOption {
+ return dummyOption
+}
+
+func daemonTimeout(name string) MountOption {
+ return dummyOption
+}
+
+func noAppleXattr(conf *mountConfig) error {
+ return nil
+}
+
+func noAppleDouble(conf *mountConfig) error {
+ return nil
+}
+
+func exclCreate(conf *mountConfig) error {
+ return nil
+}
diff --git a/vendor/bazil.org/fuse/protocol.go b/vendor/bazil.org/fuse/protocol.go
new file mode 100644
index 000000000..a77bbf72f
--- /dev/null
+++ b/vendor/bazil.org/fuse/protocol.go
@@ -0,0 +1,75 @@
+package fuse
+
+import (
+ "fmt"
+)
+
+// Protocol is a FUSE protocol version number.
+type Protocol struct {
+ Major uint32
+ Minor uint32
+}
+
+func (p Protocol) String() string {
+ return fmt.Sprintf("%d.%d", p.Major, p.Minor)
+}
+
+// LT returns whether a is less than b.
+func (a Protocol) LT(b Protocol) bool {
+ return a.Major < b.Major ||
+ (a.Major == b.Major && a.Minor < b.Minor)
+}
+
+// GE returns whether a is greater than or equal to b.
+func (a Protocol) GE(b Protocol) bool {
+ return a.Major > b.Major ||
+ (a.Major == b.Major && a.Minor >= b.Minor)
+}
+
+func (a Protocol) is79() bool {
+ return a.GE(Protocol{7, 9})
+}
+
+// HasAttrBlockSize returns whether Attr.BlockSize is respected by the
+// kernel.
+func (a Protocol) HasAttrBlockSize() bool {
+ return a.is79()
+}
+
+// HasReadWriteFlags returns whether ReadRequest/WriteRequest
+// fields Flags and FileFlags are valid.
+func (a Protocol) HasReadWriteFlags() bool {
+ return a.is79()
+}
+
+// HasGetattrFlags returns whether GetattrRequest field Flags is
+// valid.
+func (a Protocol) HasGetattrFlags() bool {
+ return a.is79()
+}
+
+func (a Protocol) is710() bool {
+ return a.GE(Protocol{7, 10})
+}
+
+// HasOpenNonSeekable returns whether OpenResponse field Flags flag
+// OpenNonSeekable is supported.
+func (a Protocol) HasOpenNonSeekable() bool {
+ return a.is710()
+}
+
+func (a Protocol) is712() bool {
+ return a.GE(Protocol{7, 12})
+}
+
+// HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest
+// field Umask is valid.
+func (a Protocol) HasUmask() bool {
+ return a.is712()
+}
+
+// HasInvalidate returns whether InvalidateNode/InvalidateEntry are
+// supported.
+func (a Protocol) HasInvalidate() bool {
+ return a.is712()
+}
diff --git a/vendor/bazil.org/fuse/unmount.go b/vendor/bazil.org/fuse/unmount.go
new file mode 100644
index 000000000..ffe3f155c
--- /dev/null
+++ b/vendor/bazil.org/fuse/unmount.go
@@ -0,0 +1,6 @@
+package fuse
+
+// Unmount tries to unmount the filesystem mounted at dir.
+func Unmount(dir string) error {
+ return unmount(dir)
+}
diff --git a/vendor/bazil.org/fuse/unmount_linux.go b/vendor/bazil.org/fuse/unmount_linux.go
new file mode 100644
index 000000000..088f0cfee
--- /dev/null
+++ b/vendor/bazil.org/fuse/unmount_linux.go
@@ -0,0 +1,21 @@
+package fuse
+
+import (
+ "bytes"
+ "errors"
+ "os/exec"
+)
+
+func unmount(dir string) error {
+ cmd := exec.Command("fusermount", "-u", dir)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ if len(output) > 0 {
+ output = bytes.TrimRight(output, "\n")
+ msg := err.Error() + ": " + string(output)
+ err = errors.New(msg)
+ }
+ return err
+ }
+ return nil
+}
diff --git a/vendor/bazil.org/fuse/unmount_std.go b/vendor/bazil.org/fuse/unmount_std.go
new file mode 100644
index 000000000..d6efe276f
--- /dev/null
+++ b/vendor/bazil.org/fuse/unmount_std.go
@@ -0,0 +1,17 @@
+// +build !linux
+
+package fuse
+
+import (
+ "os"
+ "syscall"
+)
+
+func unmount(dir string) error {
+ err := syscall.Unmount(dir, 0)
+ if err != nil {
+ err = &os.PathError{Op: "unmount", Path: dir, Err: err}
+ return err
+ }
+ return nil
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index eaaf0290d..c19d600c0 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -3,6 +3,24 @@
"ignore": "test",
"package": [
{
+ "checksumSHA1": "b5bkSc2hlmUV7PlLY6JlLwiJpiE=",
+ "path": "bazil.org/fuse",
+ "revision": "371fbbdaa8987b715bdd21d6adc4c9b20155f748",
+ "revisionTime": "2016-08-11T21:22:31Z"
+ },
+ {
+ "checksumSHA1": "389JFJTJADMtZkTIfdSnsmHVOUs=",
+ "path": "bazil.org/fuse/fs",
+ "revision": "371fbbdaa8987b715bdd21d6adc4c9b20155f748",
+ "revisionTime": "2016-08-11T21:22:31Z"
+ },
+ {
+ "checksumSHA1": "NPgkh9UWMsaTtsAAs3kPrclHT9Y=",
+ "path": "bazil.org/fuse/fuseutil",
+ "revision": "371fbbdaa8987b715bdd21d6adc4c9b20155f748",
+ "revisionTime": "2016-08-11T21:22:31Z"
+ },
+ {
"checksumSHA1": "M30X+Wqn7AnUr1numUOkQRI7ET0=",
"path": "github.com/Azure/azure-sdk-for-go/storage",
"revision": "e01c89c9c29b97413ae6ebe89a294d814ec7ca8b",