mirror of https://github.com/ethereum/go-ethereum
swarm/api: support mounting manifests via FUSE (#3690)
parent
61d2150a07
commit
11e7a712f4
@ -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 |
||||
} |
@ -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 |
||||
} |
Loading…
Reference in new issue