swarm/api: support mounting manifests via FUSE (#3690)
This commit is contained in:
parent
61d2150a07
commit
11e7a712f4
15
.travis.yml
15
.travis.yml
|
@ -5,19 +5,34 @@ matrix:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
go: 1.7.5
|
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
|
# These are the latest Go versions, only run go vet and misspell on these
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
go: 1.8
|
go: 1.8
|
||||||
script:
|
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 install
|
||||||
- go run build/ci.go test -coverage -vet -misspell
|
- go run build/ci.go test -coverage -vet -misspell
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
go: 1.8
|
go: 1.8
|
||||||
|
sudo: required
|
||||||
script:
|
script:
|
||||||
|
- brew update
|
||||||
|
- brew install caskroom/cask/brew-cask
|
||||||
|
- brew cask install osxfuse
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage -vet -misspell
|
- go run build/ci.go test -coverage -vet -misspell
|
||||||
|
|
||||||
|
|
14
build/ci.go
14
build/ci.go
|
@ -173,6 +173,20 @@ func doInstall(cmdline []string) {
|
||||||
if flag.NArg() > 0 {
|
if flag.NArg() > 0 {
|
||||||
packages = flag.Args()
|
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 {
|
if *arch == "" || *arch == runtime.GOARCH {
|
||||||
goinstall := goTool("install", buildFlags(env)...)
|
goinstall := goTool("install", buildFlags(env)...)
|
||||||
goinstall.Args = append(goinstall.Args, "-v")
|
goinstall.Args = append(goinstall.Args, "-v")
|
||||||
|
|
|
@ -122,7 +122,7 @@ func init() {
|
||||||
// Override flag defaults so bzzd can run alongside geth.
|
// Override flag defaults so bzzd can run alongside geth.
|
||||||
utils.ListenPortFlag.Value = 30399
|
utils.ListenPortFlag.Value = 30399
|
||||||
utils.IPCPathFlag.Value = utils.DirectoryString{Value: "bzzd.ipc"}
|
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.
|
// Set up the cli app.
|
||||||
app.Action = bzzd
|
app.Action = bzzd
|
||||||
|
|
|
@ -27,9 +27,12 @@ var Modules = map[string]string{
|
||||||
"personal": Personal_JS,
|
"personal": Personal_JS,
|
||||||
"rpc": RPC_JS,
|
"rpc": RPC_JS,
|
||||||
"shh": Shh_JS,
|
"shh": Shh_JS,
|
||||||
|
"swarmfs": SWARMFS_JS,
|
||||||
"txpool": TxPool_JS,
|
"txpool": TxPool_JS,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const Chequebook_JS = `
|
const Chequebook_JS = `
|
||||||
web3._extend({
|
web3._extend({
|
||||||
property: 'chequebook',
|
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 = `
|
const TxPool_JS = `
|
||||||
web3._extend({
|
web3._extend({
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -53,7 +53,8 @@ type Swarm struct {
|
||||||
privateKey *ecdsa.PrivateKey
|
privateKey *ecdsa.PrivateKey
|
||||||
corsString string
|
corsString string
|
||||||
swapEnabled bool
|
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 {
|
type SwarmAPI struct {
|
||||||
|
@ -142,6 +143,9 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
|
||||||
// Manifests for Smart Hosting
|
// Manifests for Smart Hosting
|
||||||
log.Debug(fmt.Sprintf("-> Web3 virtual server API"))
|
log.Debug(fmt.Sprintf("-> Web3 virtual server API"))
|
||||||
|
|
||||||
|
self.sfs = api.NewSwarmFS(self.api)
|
||||||
|
log.Debug("-> Initializing Fuse file system")
|
||||||
|
|
||||||
return self, nil
|
return self, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +220,7 @@ func (self *Swarm) Stop() error {
|
||||||
if self.lstore != nil {
|
if self.lstore != nil {
|
||||||
self.lstore.DbStore.Close()
|
self.lstore.DbStore.Close()
|
||||||
}
|
}
|
||||||
|
self.sfs.Stop()
|
||||||
return self.config.Save()
|
return self.config.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +244,7 @@ func (self *Swarm) APIs() []rpc.API {
|
||||||
Service: api.NewStorage(self.api),
|
Service: api.NewStorage(self.api),
|
||||||
Public: true,
|
Public: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Namespace: "bzz",
|
Namespace: "bzz",
|
||||||
Version: "0.1",
|
Version: "0.1",
|
||||||
|
@ -264,6 +269,12 @@ func (self *Swarm) APIs() []rpc.API {
|
||||||
Service: chequebook.NewApi(self.config.Swap.Chequebook),
|
Service: chequebook.NewApi(self.config.Swap.Chequebook),
|
||||||
Public: false,
|
Public: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Namespace: "swarmfs",
|
||||||
|
Version: api.Swarmfs_Version,
|
||||||
|
Service: self.sfs,
|
||||||
|
Public: false,
|
||||||
|
},
|
||||||
// {Namespace, Version, api.NewAdmin(self), false},
|
// {Namespace, Version, api.NewAdmin(self), false},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
*/
|
|
@ -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.
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fuse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ENOATTR = Errno(syscall.ENOATTR)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errNoXattr = ENOATTR
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
errnoNames[errNoXattr] = "ENOATTR"
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package fuse
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ENOATTR = Errno(syscall.ENOATTR)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errNoXattr = ENOATTR
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
errnoNames[errNoXattr] = "ENOATTR"
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fuse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ENODATA = Errno(syscall.ENODATA)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errNoXattr = ENODATA
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
errnoNames[errNoXattr] = "ENODATA"
|
||||||
|
}
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package fuse
|
|
@ -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
|
|
@ -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]
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package fuse
|
||||||
|
|
||||||
|
// Unmount tries to unmount the filesystem mounted at dir.
|
||||||
|
func Unmount(dir string) error {
|
||||||
|
return unmount(dir)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -2,6 +2,24 @@
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"ignore": "test",
|
"ignore": "test",
|
||||||
"package": [
|
"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=",
|
"checksumSHA1": "M30X+Wqn7AnUr1numUOkQRI7ET0=",
|
||||||
"path": "github.com/Azure/azure-sdk-for-go/storage",
|
"path": "github.com/Azure/azure-sdk-for-go/storage",
|
||||||
|
|
Loading…
Reference in New Issue