Commit 11e7a712 authored by Zahoor Mohamed's avatar Zahoor Mohamed Committed by Felix Lange

swarm/api: support mounting manifests via FUSE (#3690)

parent 61d2150a
......@@ -5,19 +5,34 @@ matrix:
include:
- os: linux
dist: trusty
sudo: required
go: 1.7.5
script:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
- sudo modprobe fuse
- sudo chmod 666 /dev/fuse
- sudo chown root:$USER /etc/fuse.conf
# These are the latest Go versions, only run go vet and misspell on these
- os: linux
dist: trusty
sudo: required
go: 1.8
script:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
- sudo modprobe fuse
- sudo chmod 666 /dev/fuse
- sudo chown root:$USER /etc/fuse.conf
- go run build/ci.go install
- go run build/ci.go test -coverage -vet -misspell
- os: osx
go: 1.8
sudo: required
script:
- brew update
- brew install caskroom/cask/brew-cask
- brew cask install osxfuse
- go run build/ci.go install
- go run build/ci.go test -coverage -vet -misspell
......
......@@ -173,6 +173,20 @@ func doInstall(cmdline []string) {
if flag.NArg() > 0 {
packages = flag.Args()
}
// Resolve ./... manually and remove vendor/bazil/fuse (fuse is not in windows)
out, err := goTool("list", "./...").CombinedOutput()
if err != nil {
log.Fatalf("package listing failed: %v\n%s", err, string(out))
}
packages = []string{}
for _, line := range strings.Split(string(out), "\n") {
if !strings.Contains(line, "vendor") {
packages = append(packages, strings.TrimSpace(line))
}
}
if *arch == "" || *arch == runtime.GOARCH {
goinstall := goTool("install", buildFlags(env)...)
goinstall.Args = append(goinstall.Args, "-v")
......
......@@ -122,7 +122,7 @@ func init() {
// Override flag defaults so bzzd can run alongside geth.
utils.ListenPortFlag.Value = 30399
utils.IPCPathFlag.Value = utils.DirectoryString{Value: "bzzd.ipc"}
utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, web3"
utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, swarmfs, web3"
// Set up the cli app.
app.Action = bzzd
......
......@@ -27,9 +27,12 @@ var Modules = map[string]string{
"personal": Personal_JS,
"rpc": RPC_JS,
"shh": Shh_JS,
"swarmfs": SWARMFS_JS,
"txpool": TxPool_JS,
}
const Chequebook_JS = `
web3._extend({
property: 'chequebook',
......@@ -486,6 +489,32 @@ web3._extend({
]
});
`
const SWARMFS_JS = `
web3._extend({
property: 'swarmfs',
methods:
[
new web3._extend.Method({
name: 'mount',
call: 'swarmfs_mount',
params: 2,
inputFormatter: [null,null]
}),
new web3._extend.Method({
name: 'unmount',
call: 'swarmfs_unmount',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Method({
name: 'listmounts',
call: 'swarmfs_listmounts',
params: 0,
inputFormatter: []
})
]
});
`
const TxPool_JS = `
web3._extend({
......
// 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
}
// 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
}
// 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
}
// 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")
})
}
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// +build windows
package api
import (
"github.com/ethereum/go-ethereum/log"
)
// Dummy struct and functions to satsfy windows build
type MountInfo struct {
}
func (self *SwarmFS) Mount(mhash, mountpoint string) error {
log.Info("Platform not supported")
return nil
}
func (self *SwarmFS) Unmount(mountpoint string) error {
log.Info("Platform not supported")
return nil
}
func (self *SwarmFS) Listmounts() (string, error) {
log.Info("Platform not supported")
return "",nil
}
func (self *SwarmFS) Stop() error {
log.Info("Platform not supported")
return nil
}
\ No newline at end of file
......@@ -53,7 +53,8 @@ type Swarm struct {
privateKey *ecdsa.PrivateKey
corsString string
swapEnabled bool
lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped
lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped
sfs *api.SwarmFS // need this to cleanup all the active mounts on node exit
}
type SwarmAPI struct {
......@@ -142,6 +143,9 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
// Manifests for Smart Hosting
log.Debug(fmt.Sprintf("-> Web3 virtual server API"))
self.sfs = api.NewSwarmFS(self.api)
log.Debug("-> Initializing Fuse file system")
return self, nil
}
......@@ -216,7 +220,7 @@ func (self *Swarm) Stop() error {
if self.lstore != nil {
self.lstore.DbStore.Close()
}
self.sfs.Stop()
return self.config.Save()
}
......@@ -240,6 +244,7 @@ func (self *Swarm) APIs() []rpc.API {
Service: api.NewStorage(self.api),
Public: true,
},
{
Namespace: "bzz",
Version: "0.1",
......@@ -264,6 +269,12 @@ func (self *Swarm) APIs() []rpc.API {
Service: chequebook.NewApi(self.config.Swap.Chequebook),
Public: false,
},
{
Namespace: "swarmfs",
Version: api.Swarmfs_Version,
Service: self.sfs,
Public: false,
},
// {Namespace, Version, api.NewAdmin(self), false},
}
}
......
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.
*/
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.
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
}
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
package fuse
import (
"syscall"
)
const (
ENOATTR = Errno(syscall.ENOATTR)
)
const (
errNoXattr = ENOATTR
)
func init() {
errnoNames[errNoXattr] = "ENOATTR"
}
package fuse
import "syscall"
const (
ENOATTR = Errno(syscall.ENOATTR)
)
const (
errNoXattr = ENOATTR
)
func init() {
errnoNames[errNoXattr] = "ENOATTR"
}
package fuse
import (
"syscall"
)
const (
ENODATA = Errno(syscall.ENODATA)
)
const (
errNoXattr = ENODATA
)
func init() {
errnoNames[errNoXattr] = "ENODATA"
}
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
This diff is collapsed.
// 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
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment