swarm: codebase split from go-ethereum (#1405)
This commit is contained in:
committed by
Anton Evangelatov
parent
7a22da98b9
commit
b046760db1
161
fuse/fuse_dir.go
Normal file
161
fuse/fuse_dir.go
Normal file
@@ -0,0 +1,161 @@
|
||||
// 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 freebsd
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
_ fs.Node = (*SwarmDir)(nil)
|
||||
_ fs.NodeRequestLookuper = (*SwarmDir)(nil)
|
||||
_ fs.HandleReadDirAller = (*SwarmDir)(nil)
|
||||
_ fs.NodeCreater = (*SwarmDir)(nil)
|
||||
_ fs.NodeRemover = (*SwarmDir)(nil)
|
||||
_ fs.NodeMkdirer = (*SwarmDir)(nil)
|
||||
)
|
||||
|
||||
type SwarmDir struct {
|
||||
inode uint64
|
||||
name string
|
||||
path string
|
||||
directories []*SwarmDir
|
||||
files []*SwarmFile
|
||||
|
||||
mountInfo *MountInfo
|
||||
lock *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewSwarmDir(fullpath string, minfo *MountInfo) *SwarmDir {
|
||||
log.Debug("swarmfs", "NewSwarmDir", fullpath)
|
||||
newdir := &SwarmDir{
|
||||
inode: NewInode(),
|
||||
name: filepath.Base(fullpath),
|
||||
path: fullpath,
|
||||
directories: []*SwarmDir{},
|
||||
files: []*SwarmFile{},
|
||||
mountInfo: minfo,
|
||||
lock: &sync.RWMutex{},
|
||||
}
|
||||
return newdir
|
||||
}
|
||||
|
||||
func (sd *SwarmDir) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
sd.lock.RLock()
|
||||
defer sd.lock.RUnlock()
|
||||
a.Inode = sd.inode
|
||||
a.Mode = os.ModeDir | 0700
|
||||
a.Uid = uint32(os.Getuid())
|
||||
a.Gid = uint32(os.Getegid())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sd *SwarmDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) {
|
||||
log.Debug("swarmfs", "Lookup", req.Name)
|
||||
for _, n := range sd.files {
|
||||
if n.name == req.Name {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
for _, n := range sd.directories {
|
||||
if n.name == req.Name {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func (sd *SwarmDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
log.Debug("swarmfs ReadDirAll")
|
||||
var children []fuse.Dirent
|
||||
for _, file := range sd.files {
|
||||
children = append(children, fuse.Dirent{Inode: file.inode, Type: fuse.DT_File, Name: file.name})
|
||||
}
|
||||
for _, dir := range sd.directories {
|
||||
children = append(children, fuse.Dirent{Inode: dir.inode, Type: fuse.DT_Dir, Name: dir.name})
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
|
||||
func (sd *SwarmDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
||||
log.Debug("swarmfs Create", "path", sd.path, "req.Name", req.Name)
|
||||
|
||||
newFile := NewSwarmFile(sd.path, req.Name, sd.mountInfo)
|
||||
newFile.fileSize = 0 // 0 means, file is not in swarm yet and it is just created
|
||||
|
||||
sd.lock.Lock()
|
||||
defer sd.lock.Unlock()
|
||||
sd.files = append(sd.files, newFile)
|
||||
|
||||
return newFile, newFile, nil
|
||||
}
|
||||
|
||||
func (sd *SwarmDir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
|
||||
log.Debug("swarmfs Remove", "path", sd.path, "req.Name", req.Name)
|
||||
|
||||
if req.Dir && sd.directories != nil {
|
||||
newDirs := []*SwarmDir{}
|
||||
for _, dir := range sd.directories {
|
||||
if dir.name == req.Name {
|
||||
removeDirectoryFromSwarm(dir)
|
||||
} else {
|
||||
newDirs = append(newDirs, dir)
|
||||
}
|
||||
}
|
||||
if len(sd.directories) > len(newDirs) {
|
||||
sd.lock.Lock()
|
||||
defer sd.lock.Unlock()
|
||||
sd.directories = newDirs
|
||||
}
|
||||
return nil
|
||||
} else if !req.Dir && sd.files != nil {
|
||||
newFiles := []*SwarmFile{}
|
||||
for _, f := range sd.files {
|
||||
if f.name == req.Name {
|
||||
removeFileFromSwarm(f)
|
||||
} else {
|
||||
newFiles = append(newFiles, f)
|
||||
}
|
||||
}
|
||||
if len(sd.files) > len(newFiles) {
|
||||
sd.lock.Lock()
|
||||
defer sd.lock.Unlock()
|
||||
sd.files = newFiles
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fuse.ENOENT
|
||||
}
|
||||
|
||||
func (sd *SwarmDir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
|
||||
log.Debug("swarmfs Mkdir", "path", sd.path, "req.Name", req.Name)
|
||||
newDir := NewSwarmDir(filepath.Join(sd.path, req.Name), sd.mountInfo)
|
||||
sd.lock.Lock()
|
||||
defer sd.lock.Unlock()
|
||||
sd.directories = append(sd.directories, newDir)
|
||||
|
||||
return newDir, nil
|
||||
}
|
146
fuse/fuse_file.go
Normal file
146
fuse/fuse_file.go
Normal file
@@ -0,0 +1,146 @@
|
||||
// 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 freebsd
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxAppendFileSize = 10485760 // 10Mb
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidOffset = errors.New("Invalid offset during write")
|
||||
errFileSizeMaxLimixReached = errors.New("File size exceeded max limit")
|
||||
)
|
||||
|
||||
var (
|
||||
_ fs.Node = (*SwarmFile)(nil)
|
||||
_ fs.HandleReader = (*SwarmFile)(nil)
|
||||
_ fs.HandleWriter = (*SwarmFile)(nil)
|
||||
)
|
||||
|
||||
type SwarmFile struct {
|
||||
inode uint64
|
||||
name string
|
||||
path string
|
||||
addr storage.Address
|
||||
fileSize int64
|
||||
reader storage.LazySectionReader
|
||||
|
||||
mountInfo *MountInfo
|
||||
lock *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewSwarmFile(path, fname string, minfo *MountInfo) *SwarmFile {
|
||||
newFile := &SwarmFile{
|
||||
inode: NewInode(),
|
||||
name: fname,
|
||||
path: path,
|
||||
addr: nil,
|
||||
fileSize: -1, // -1 means , file already exists in swarm and you need to just get the size from swarm
|
||||
reader: nil,
|
||||
|
||||
mountInfo: minfo,
|
||||
lock: &sync.RWMutex{},
|
||||
}
|
||||
return newFile
|
||||
}
|
||||
|
||||
func (sf *SwarmFile) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
log.Debug("swarmfs Attr", "path", sf.path)
|
||||
sf.lock.Lock()
|
||||
defer sf.lock.Unlock()
|
||||
a.Inode = sf.inode
|
||||
//TODO: need to get permission as argument
|
||||
a.Mode = 0700
|
||||
a.Uid = uint32(os.Getuid())
|
||||
a.Gid = uint32(os.Getegid())
|
||||
|
||||
if sf.fileSize == -1 {
|
||||
reader, _ := sf.mountInfo.swarmApi.Retrieve(ctx, sf.addr)
|
||||
quitC := make(chan bool)
|
||||
size, err := reader.Size(ctx, quitC)
|
||||
if err != nil {
|
||||
log.Error("Couldnt get size of file %s : %v", sf.path, err)
|
||||
return err
|
||||
}
|
||||
sf.fileSize = size
|
||||
log.Trace("swarmfs Attr", "size", size)
|
||||
close(quitC)
|
||||
}
|
||||
a.Size = uint64(sf.fileSize)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sf *SwarmFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
||||
log.Debug("swarmfs Read", "path", sf.path, "req.String", req.String())
|
||||
sf.lock.RLock()
|
||||
defer sf.lock.RUnlock()
|
||||
if sf.reader == nil {
|
||||
sf.reader, _ = sf.mountInfo.swarmApi.Retrieve(ctx, sf.addr)
|
||||
}
|
||||
buf := make([]byte, req.Size)
|
||||
n, err := sf.reader.ReadAt(buf, req.Offset)
|
||||
if err == io.ErrUnexpectedEOF || err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
resp.Data = buf[:n]
|
||||
sf.reader = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sf *SwarmFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
|
||||
log.Debug("swarmfs Write", "path", sf.path, "req.String", req.String())
|
||||
if sf.fileSize == 0 && req.Offset == 0 {
|
||||
// A new file is created
|
||||
err := addFileToSwarm(sf, req.Data, len(req.Data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Size = len(req.Data)
|
||||
} else if req.Offset <= sf.fileSize {
|
||||
totalSize := sf.fileSize + int64(len(req.Data))
|
||||
if totalSize > MaxAppendFileSize {
|
||||
log.Warn("swarmfs Append file size reached (%v) : (%v)", sf.fileSize, len(req.Data))
|
||||
return errFileSizeMaxLimixReached
|
||||
}
|
||||
|
||||
err := appendToExistingFileInSwarm(sf, req.Data, req.Offset, int64(len(req.Data)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Size = len(req.Data)
|
||||
} else {
|
||||
log.Warn("swarmfs Invalid write request size(%v) : off(%v)", sf.fileSize, req.Offset)
|
||||
return errInvalidOffset
|
||||
}
|
||||
return nil
|
||||
}
|
35
fuse/fuse_root.go
Normal file
35
fuse/fuse_root.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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 freebsd
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bazil.org/fuse/fs"
|
||||
)
|
||||
|
||||
var (
|
||||
_ fs.Node = (*SwarmDir)(nil)
|
||||
)
|
||||
|
||||
type SwarmRoot struct {
|
||||
root *SwarmDir
|
||||
}
|
||||
|
||||
func (filesystem *SwarmRoot) Root() (fs.Node, error) {
|
||||
return filesystem.root, nil
|
||||
}
|
65
fuse/swarmfs.go
Normal file
65
fuse/swarmfs.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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 fuse
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethersphere/swarm/api"
|
||||
)
|
||||
|
||||
const (
|
||||
SwarmFSVersion = "0.1"
|
||||
mountTimeout = time.Second * 5
|
||||
unmountTimeout = time.Second * 10
|
||||
maxFUSEMounts = 5
|
||||
)
|
||||
|
||||
var (
|
||||
swarmfs *SwarmFS // Swarm file system singleton
|
||||
swarmfsLock sync.Once
|
||||
|
||||
inode uint64 = 1 // global inode
|
||||
inodeLock sync.RWMutex
|
||||
)
|
||||
|
||||
type SwarmFS struct {
|
||||
swarmApi *api.API
|
||||
activeMounts map[string]*MountInfo
|
||||
swarmFsLock *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewSwarmFS(api *api.API) *SwarmFS {
|
||||
swarmfsLock.Do(func() {
|
||||
swarmfs = &SwarmFS{
|
||||
swarmApi: api,
|
||||
swarmFsLock: &sync.RWMutex{},
|
||||
activeMounts: map[string]*MountInfo{},
|
||||
}
|
||||
})
|
||||
return swarmfs
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
51
fuse/swarmfs_fallback.go
Normal file
51
fuse/swarmfs_fallback.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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,!freebsd
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var errNoFUSE = errors.New("FUSE is not supported on this platform")
|
||||
|
||||
func isFUSEUnsupportedError(err error) bool {
|
||||
return err == errNoFUSE
|
||||
}
|
||||
|
||||
type MountInfo struct {
|
||||
MountPoint string
|
||||
StartManifest string
|
||||
LatestManifest string
|
||||
}
|
||||
|
||||
func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) {
|
||||
return nil, errNoFUSE
|
||||
}
|
||||
|
||||
func (self *SwarmFS) Unmount(mountpoint string) (bool, error) {
|
||||
return false, errNoFUSE
|
||||
}
|
||||
|
||||
func (self *SwarmFS) Listmounts() ([]*MountInfo, error) {
|
||||
return nil, errNoFUSE
|
||||
}
|
||||
|
||||
func (self *SwarmFS) Stop() error {
|
||||
return nil
|
||||
}
|
1672
fuse/swarmfs_test.go
Normal file
1672
fuse/swarmfs_test.go
Normal file
File diff suppressed because it is too large
Load Diff
285
fuse/swarmfs_unix.go
Normal file
285
fuse/swarmfs_unix.go
Normal file
@@ -0,0 +1,285 @@
|
||||
// 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 freebsd
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethersphere/swarm/api"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
)
|
||||
|
||||
var (
|
||||
errEmptyMountPoint = errors.New("need non-empty mount point")
|
||||
errNoRelativeMountPoint = errors.New("invalid path for mount point (need absolute path)")
|
||||
errMaxMountCount = errors.New("max FUSE mount count reached")
|
||||
errMountTimeout = errors.New("mount timeout")
|
||||
errAlreadyMounted = errors.New("mount point is already serving")
|
||||
)
|
||||
|
||||
func isFUSEUnsupportedError(err error) bool {
|
||||
if perr, ok := err.(*os.PathError); ok {
|
||||
return perr.Op == "open" && perr.Path == "/dev/fuse"
|
||||
}
|
||||
return err == fuse.ErrOSXFUSENotFound
|
||||
}
|
||||
|
||||
// MountInfo contains information about every active mount
|
||||
type MountInfo struct {
|
||||
MountPoint string
|
||||
StartManifest string
|
||||
LatestManifest string
|
||||
rootDir *SwarmDir
|
||||
fuseConnection *fuse.Conn
|
||||
swarmApi *api.API
|
||||
lock *sync.RWMutex
|
||||
serveClose chan struct{}
|
||||
}
|
||||
|
||||
func NewMountInfo(mhash, mpoint string, sapi *api.API) *MountInfo {
|
||||
log.Debug("swarmfs NewMountInfo", "hash", mhash, "mount point", mpoint)
|
||||
newMountInfo := &MountInfo{
|
||||
MountPoint: mpoint,
|
||||
StartManifest: mhash,
|
||||
LatestManifest: mhash,
|
||||
rootDir: nil,
|
||||
fuseConnection: nil,
|
||||
swarmApi: sapi,
|
||||
lock: &sync.RWMutex{},
|
||||
serveClose: make(chan struct{}),
|
||||
}
|
||||
return newMountInfo
|
||||
}
|
||||
|
||||
func (swarmfs *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) {
|
||||
log.Info("swarmfs", "mounting hash", mhash, "mount point", mountpoint)
|
||||
if mountpoint == "" {
|
||||
return nil, errEmptyMountPoint
|
||||
}
|
||||
if !strings.HasPrefix(mountpoint, "/") {
|
||||
return nil, errNoRelativeMountPoint
|
||||
}
|
||||
cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace("swarmfs mount", "cleanedMountPoint", cleanedMountPoint)
|
||||
|
||||
swarmfs.swarmFsLock.Lock()
|
||||
defer swarmfs.swarmFsLock.Unlock()
|
||||
|
||||
noOfActiveMounts := len(swarmfs.activeMounts)
|
||||
log.Debug("swarmfs mount", "# active mounts", noOfActiveMounts)
|
||||
if noOfActiveMounts >= maxFUSEMounts {
|
||||
return nil, errMaxMountCount
|
||||
}
|
||||
|
||||
if _, ok := swarmfs.activeMounts[cleanedMountPoint]; ok {
|
||||
return nil, errAlreadyMounted
|
||||
}
|
||||
|
||||
log.Trace("swarmfs mount: getting manifest tree")
|
||||
_, manifestEntryMap, err := swarmfs.swarmApi.BuildDirectoryTree(context.TODO(), mhash, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Trace("swarmfs mount: building mount info")
|
||||
mi := NewMountInfo(mhash, cleanedMountPoint, swarmfs.swarmApi)
|
||||
|
||||
dirTree := map[string]*SwarmDir{}
|
||||
rootDir := NewSwarmDir("/", mi)
|
||||
log.Trace("swarmfs mount", "rootDir", rootDir)
|
||||
mi.rootDir = rootDir
|
||||
|
||||
log.Trace("swarmfs mount: traversing manifest map")
|
||||
for suffix, entry := range manifestEntryMap {
|
||||
if suffix == "" { //empty suffix means that the file has no name - i.e. this is the default entry in a manifest. Since we cannot have files without a name, let us ignore this entry
|
||||
log.Warn("Manifest has an empty-path (default) entry which will be ignored in FUSE mount.")
|
||||
continue
|
||||
}
|
||||
addr := common.Hex2Bytes(entry.Hash)
|
||||
fullpath := "/" + suffix
|
||||
basepath := filepath.Dir(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] = NewSwarmDir(dirUntilNow, mi)
|
||||
parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow])
|
||||
parentDir = dirTree[dirUntilNow]
|
||||
|
||||
} else {
|
||||
parentDir = dirTree[dirUntilNow]
|
||||
}
|
||||
}
|
||||
}
|
||||
thisFile := NewSwarmFile(basepath, filepath.Base(fullpath), mi)
|
||||
thisFile.addr = addr
|
||||
|
||||
parentDir.files = append(parentDir.files, thisFile)
|
||||
}
|
||||
|
||||
fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash))
|
||||
if isFUSEUnsupportedError(err) {
|
||||
log.Error("swarmfs error - FUSE not installed", "mountpoint", cleanedMountPoint, "err", err)
|
||||
return nil, err
|
||||
} else if err != nil {
|
||||
fuse.Unmount(cleanedMountPoint)
|
||||
log.Error("swarmfs error mounting swarm manifest", "mountpoint", cleanedMountPoint, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
mi.fuseConnection = fconn
|
||||
|
||||
serverr := make(chan error, 1)
|
||||
go func() {
|
||||
log.Info("swarmfs", "serving hash", mhash, "at", cleanedMountPoint)
|
||||
filesys := &SwarmRoot{root: rootDir}
|
||||
//start serving the actual file system; see note below
|
||||
if err := fs.Serve(fconn, filesys); err != nil {
|
||||
log.Warn("swarmfs could not serve the requested hash", "error", err)
|
||||
serverr <- err
|
||||
}
|
||||
mi.serveClose <- struct{}{}
|
||||
}()
|
||||
|
||||
/*
|
||||
IMPORTANT NOTE: the fs.Serve function is blocking;
|
||||
Serve builds up the actual fuse file system by calling the
|
||||
Attr functions on each SwarmFile, creating the file inodes;
|
||||
specifically calling the swarm's LazySectionReader.Size() to set the file size.
|
||||
|
||||
This can take some time, and it appears that if we access the fuse file system
|
||||
too early, we can bring the tests to deadlock. The assumption so far is that
|
||||
at this point, the fuse driver didn't finish to initialize the file system.
|
||||
|
||||
Accessing files too early not only deadlocks the tests, but locks the access
|
||||
of the fuse file completely, resulting in blocked resources at OS system level.
|
||||
Even a simple `ls /tmp/testDir/testMountDir` could deadlock in a shell.
|
||||
|
||||
Workaround so far is to wait some time to give the OS enough time to initialize
|
||||
the fuse file system. During tests, this seemed to address the issue.
|
||||
|
||||
HOWEVER IT SHOULD BE NOTED THAT THIS MAY ONLY BE AN EFFECT,
|
||||
AND THE DEADLOCK CAUSED BY SOMETHING ELSE BLOCKING ACCESS DUE TO SOME RACE CONDITION
|
||||
(caused in the bazil.org library and/or the SwarmRoot, SwarmDir and SwarmFile implementations)
|
||||
*/
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
timer := time.NewTimer(mountTimeout)
|
||||
defer timer.Stop()
|
||||
// Check if the mount process has an error to report.
|
||||
select {
|
||||
case <-timer.C:
|
||||
log.Warn("swarmfs timed out mounting over FUSE", "mountpoint", cleanedMountPoint, "err", err)
|
||||
err := fuse.Unmount(cleanedMountPoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errMountTimeout
|
||||
case err := <-serverr:
|
||||
log.Warn("swarmfs error serving over FUSE", "mountpoint", cleanedMountPoint, "err", err)
|
||||
err = fuse.Unmount(cleanedMountPoint)
|
||||
return nil, err
|
||||
|
||||
case <-fconn.Ready:
|
||||
//this signals that the actual mount point from the fuse.Mount call is ready;
|
||||
//it does not signal though that the file system from fs.Serve is actually fully built up
|
||||
if err := fconn.MountError; err != nil {
|
||||
log.Error("Mounting error from fuse driver: ", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
log.Info("swarmfs now served over FUSE", "manifest", mhash, "mountpoint", cleanedMountPoint)
|
||||
}
|
||||
|
||||
timer.Stop()
|
||||
swarmfs.activeMounts[cleanedMountPoint] = mi
|
||||
return mi, nil
|
||||
}
|
||||
|
||||
func (swarmfs *SwarmFS) Unmount(mountpoint string) (*MountInfo, error) {
|
||||
swarmfs.swarmFsLock.Lock()
|
||||
defer swarmfs.swarmFsLock.Unlock()
|
||||
|
||||
cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mountInfo := swarmfs.activeMounts[cleanedMountPoint]
|
||||
|
||||
if mountInfo == nil || mountInfo.MountPoint != cleanedMountPoint {
|
||||
return nil, fmt.Errorf("swarmfs %s is not mounted", cleanedMountPoint)
|
||||
}
|
||||
err = fuse.Unmount(cleanedMountPoint)
|
||||
if err != nil {
|
||||
err1 := externalUnmount(cleanedMountPoint)
|
||||
if err1 != nil {
|
||||
errStr := fmt.Sprintf("swarmfs unmount error: %v", err)
|
||||
log.Warn(errStr)
|
||||
return nil, err1
|
||||
}
|
||||
}
|
||||
|
||||
err = mountInfo.fuseConnection.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(swarmfs.activeMounts, cleanedMountPoint)
|
||||
|
||||
<-mountInfo.serveClose
|
||||
|
||||
succString := fmt.Sprintf("swarmfs unmounting %v succeeded", cleanedMountPoint)
|
||||
log.Info(succString)
|
||||
|
||||
return mountInfo, nil
|
||||
}
|
||||
|
||||
func (swarmfs *SwarmFS) Listmounts() []*MountInfo {
|
||||
swarmfs.swarmFsLock.RLock()
|
||||
defer swarmfs.swarmFsLock.RUnlock()
|
||||
rows := make([]*MountInfo, 0, len(swarmfs.activeMounts))
|
||||
for _, mi := range swarmfs.activeMounts {
|
||||
rows = append(rows, mi)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (swarmfs *SwarmFS) Stop() bool {
|
||||
for mp := range swarmfs.activeMounts {
|
||||
mountInfo := swarmfs.activeMounts[mp]
|
||||
swarmfs.Unmount(mountInfo.MountPoint)
|
||||
}
|
||||
return true
|
||||
}
|
121
fuse/swarmfs_util.go
Normal file
121
fuse/swarmfs_util.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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 freebsd
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/ethersphere/swarm/log"
|
||||
)
|
||||
|
||||
func externalUnmount(mountPoint string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), unmountTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Try generic umount.
|
||||
if err := exec.CommandContext(ctx, "umount", mountPoint).Run(); err == nil {
|
||||
return nil
|
||||
}
|
||||
// Try FUSE-specific commands if umount didn't work.
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return exec.CommandContext(ctx, "diskutil", "umount", mountPoint).Run()
|
||||
case "linux":
|
||||
return exec.CommandContext(ctx, "fusermount", "-u", mountPoint).Run()
|
||||
default:
|
||||
return fmt.Errorf("swarmfs unmount: unimplemented")
|
||||
}
|
||||
}
|
||||
|
||||
func addFileToSwarm(sf *SwarmFile, content []byte, size int) error {
|
||||
fkey, mhash, err := sf.mountInfo.swarmApi.AddFile(context.TODO(), sf.mountInfo.LatestManifest, sf.path, sf.name, content, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sf.lock.Lock()
|
||||
defer sf.lock.Unlock()
|
||||
sf.addr = fkey
|
||||
sf.fileSize = int64(size)
|
||||
|
||||
sf.mountInfo.lock.Lock()
|
||||
defer sf.mountInfo.lock.Unlock()
|
||||
sf.mountInfo.LatestManifest = mhash
|
||||
|
||||
log.Info("swarmfs added new file:", "fname", sf.name, "new Manifest hash", mhash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeFileFromSwarm(sf *SwarmFile) error {
|
||||
mkey, err := sf.mountInfo.swarmApi.RemoveFile(context.TODO(), sf.mountInfo.LatestManifest, sf.path, sf.name, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sf.mountInfo.lock.Lock()
|
||||
defer sf.mountInfo.lock.Unlock()
|
||||
sf.mountInfo.LatestManifest = mkey
|
||||
|
||||
log.Info("swarmfs removed file:", "fname", sf.name, "new Manifest hash", mkey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeDirectoryFromSwarm(sd *SwarmDir) error {
|
||||
if len(sd.directories) == 0 && len(sd.files) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, d := range sd.directories {
|
||||
err := removeDirectoryFromSwarm(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range sd.files {
|
||||
err := removeFileFromSwarm(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendToExistingFileInSwarm(sf *SwarmFile, content []byte, offset int64, length int64) error {
|
||||
fkey, mhash, err := sf.mountInfo.swarmApi.AppendFile(context.TODO(), sf.mountInfo.LatestManifest, sf.path, sf.name, sf.fileSize, content, sf.addr, offset, length, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sf.lock.Lock()
|
||||
defer sf.lock.Unlock()
|
||||
sf.addr = fkey
|
||||
sf.fileSize = sf.fileSize + int64(len(content))
|
||||
|
||||
sf.mountInfo.lock.Lock()
|
||||
defer sf.mountInfo.lock.Unlock()
|
||||
sf.mountInfo.LatestManifest = mhash
|
||||
|
||||
log.Info("swarmfs appended file:", "fname", sf.name, "new Manifest hash", mhash)
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user