@ -14,77 +14,86 @@
// You should have received a copy of the GNU Lesser General Public License
// 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/>.
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// +build linux darwin
// +build linux darwin freebsd
package api
package api
import (
import (
"path/filepath "
"errors "
"fmt"
"fmt"
"os"
"path/filepath"
"strings"
"strings"
"sync"
"time"
"time"
"github.com/ethereum/go-ethereum/swarm/storage"
"bazil.org/fuse"
"bazil.org/fuse"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/common"
"bazil.org/fuse/fs"
"bazil.org/fuse/fs"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/storage"
)
)
var (
inode uint64 = 1
inodeLock sync . RWMutex
)
var (
var (
inode uint64 = 1
errEmptyMountPoint = errors . New ( "need non-empty mount point" )
inodeLock sync . RWMutex
errMaxMountCount = errors . New ( "max FUSE mount count reached" )
errMountTimeout = errors . New ( "mount timeout" )
)
)
// information about every active mount
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 {
type MountInfo struct {
mountPoint string
M ountPoint string
manifestHash string
M anifestHash string
resolvedKey storage . Key
resolvedKey storage . Key
rootDir * Dir
rootDir * Dir
fuseConnection * fuse . Conn
fuseConnection * fuse . Conn
}
}
// newInode creates a new inode number.
// Inode numbers need to be unique, they are used for caching inside fuse
// Inode numbers need to be unique, they are used for caching inside fuse
func N ewInode( ) uint64 {
func n ewInode( ) uint64 {
inodeLock . Lock ( )
inodeLock . Lock ( )
defer inodeLock . Unlock ( )
defer inodeLock . Unlock ( )
inode += 1
inode += 1
return inode
return inode
}
}
func ( self * SwarmFS ) Mount ( mhash , mountpoint string ) ( * MountInfo , error ) {
if mountpoint == "" {
func ( self * SwarmFS ) Mount ( mhash , mountpoint string ) ( string , error ) {
return nil , errEmptyMountPoint
}
cleanedMountPoint , err := filepath . Abs ( filepath . Clean ( mountpoint ) )
if err != nil {
return nil , err
}
self . activeLock . Lock ( )
self . activeLock . Lock ( )
defer self . activeLock . Unlock ( )
defer self . activeLock . Unlock ( )
noOfActiveMounts := len ( self . activeMounts )
noOfActiveMounts := len ( self . activeMounts )
if noOfActiveMounts >= maxFuseMounts {
if noOfActiveMounts >= maxFuseMounts {
err := fmt . Errorf ( "Max mount count reached. Cannot mount %s " , mountpoint )
return nil , errMaxMountCount
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 {
if _ , ok := self . activeMounts [ cleanedMountPoint ] ; ok {
err := fmt . Errorf ( "Mountpoint %s already mounted." , cleanedMountPoint )
return nil , fmt . Errorf ( "%s is 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 )
key , _ , path , err := self . swarmApi . parseAndResolve ( mhash , true )
if err != nil {
if err != nil {
errStr := fmt . Sprintf ( "Could not resolve %s : %v" , mhash , err )
return nil , fmt . Errorf ( "can't resolve %q: %v" , mhash , err )
log . Warn ( errStr )
return errStr , err
}
}
if len ( path ) > 0 {
if len ( path ) > 0 {
@ -94,15 +103,13 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (string, error) {
quitC := make ( chan bool )
quitC := make ( chan bool )
trie , err := loadManifest ( self . swarmApi . dpa , key , quitC )
trie , err := loadManifest ( self . swarmApi . dpa , key , quitC )
if err != nil {
if err != nil {
errStr := fmt . Sprintf ( "fs.Download: loadManifestTrie error: %v" , err )
return nil , fmt . Errorf ( "can't load manifest %v: %v" , key . String ( ) , err )
log . Warn ( errStr )
return errStr , err
}
}
dirTree := map [ string ] * Dir { }
dirTree := map [ string ] * Dir { }
rootDir := & Dir {
rootDir := & Dir {
inode : N ewInode( ) ,
inode : n ewInode( ) ,
name : "root" ,
name : "root" ,
directories : nil ,
directories : nil ,
files : nil ,
files : nil ,
@ -110,7 +117,6 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (string, error) {
dirTree [ "root" ] = rootDir
dirTree [ "root" ] = rootDir
err = trie . listWithPrefix ( path , quitC , func ( entry * manifestTrieEntry , suffix string ) {
err = trie . listWithPrefix ( path , quitC , func ( entry * manifestTrieEntry , suffix string ) {
key = common . Hex2Bytes ( entry . Hash )
key = common . Hex2Bytes ( entry . Hash )
fullpath := "/" + suffix
fullpath := "/" + suffix
basepath := filepath . Dir ( fullpath )
basepath := filepath . Dir ( fullpath )
@ -126,7 +132,7 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (string, error) {
if _ , ok := dirTree [ dirUntilNow ] ; ! ok {
if _ , ok := dirTree [ dirUntilNow ] ; ! ok {
dirTree [ dirUntilNow ] = & Dir {
dirTree [ dirUntilNow ] = & Dir {
inode : N ewInode( ) ,
inode : n ewInode( ) ,
name : thisDir ,
name : thisDir ,
path : dirUntilNow ,
path : dirUntilNow ,
directories : nil ,
directories : nil ,
@ -142,7 +148,7 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (string, error) {
}
}
}
}
thisFile := & File {
thisFile := & File {
inode : N ewInode( ) ,
inode : n ewInode( ) ,
name : filename ,
name : filename ,
path : fullpath ,
path : fullpath ,
key : key ,
key : key ,
@ -154,113 +160,84 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (string, error) {
fconn , err := fuse . Mount ( cleanedMountPoint , fuse . FSName ( "swarmfs" ) , fuse . VolumeName ( mhash ) )
fconn , err := fuse . Mount ( cleanedMountPoint , fuse . FSName ( "swarmfs" ) , fuse . VolumeName ( mhash ) )
if err != nil {
if err != nil {
fuse . Unmount ( cleanedMountPoint )
fuse . Unmount ( cleanedMountPoint )
errStr := fmt . Sprintf ( "Mounting %s encountered error: %v" , cleanedMountPoint , err )
log . Warn ( "Error mounting swarm manifest" , "mountpoint" , cleanedMountPoint , "err" , err )
log . Warn ( errStr )
return nil , err
return errStr , err
}
}
mounterr := make ( chan error , 1 )
mounterr := make ( chan error , 1 )
go func ( ) {
go func ( ) {
log . Info ( fmt . Sprintf ( "Serving %s at %s" , mhash , cleanedMountPoint ) )
filesys := & FS { root : rootDir }
filesys := & FS { root : rootDir }
if err := fs . Serve ( fconn , filesys ) ; err != nil {
if err := fs . Serve ( fconn , filesys ) ; err != nil {
log . Warn ( fmt . Sprintf ( "Could not Serve FS error: %v" , err ) )
mounterr <- err
}
}
} ( )
} ( )
// Check if the mount process has an error to report.
// Check if the mount process has an error to report.
select {
select {
case <- time . After ( mountTimeout ) :
case <- time . After ( mountTimeout ) :
err := fmt . Errorf ( "Mounting %s timed out." , cleanedMountPoint )
fuse . Unmount ( cleanedMountPoint )
log . Warn ( err . Error ( ) )
return nil , errMountTimeout
return err . Error ( ) , err
case err := <- mounterr :
case err := <- mounterr :
errStr := fmt . Sprintf ( "Mounting %s encountered error: %v" , cleanedMountPoint , err )
log . Warn ( "Error serving swarm FUSE FS" , "mountpoint" , cleanedMountPoint , "err" , err )
log . Warn ( errStr )
return nil , err
return errStr , err
case <- fconn . Ready :
case <- fconn . Ready :
log . Debug ( fmt . Sprintf ( "Mounting connection succeeded for : %v ", cleanedMountPoint ) )
log . Info ( "Now serving swarm FUSE FS" , "manifest" , mhash , "mountpoint ", cleanedMountPoint )
}
}
// Assemble and Store the mount information for future use
mi := & MountInfo {
//Assemble and Store the mount information for future use
MountPoint : cleanedMountPoint ,
mountInformation := & MountInfo {
ManifestHash : mhash ,
mountPoint : cleanedMountPoint ,
manifestHash : mhash ,
resolvedKey : key ,
resolvedKey : key ,
rootDir : rootDir ,
rootDir : rootDir ,
fuseConnection : fconn ,
fuseConnection : fconn ,
}
}
self . activeMounts [ cleanedMountPoint ] = mountInformation
self . activeMounts [ cleanedMountPoint ] = mi
return mi , nil
succString := fmt . Sprintf ( "Mounting successful for %s" , cleanedMountPoint )
log . Info ( succString )
return succString , nil
}
}
func ( self * SwarmFS ) Unmount ( mountpoint string ) ( string , error ) {
func ( self * SwarmFS ) Unmount ( mountpoint string ) ( bool , error ) {
self . activeLock . Lock ( )
self . activeLock . Lock ( )
defer self . activeLock . Unlock ( )
defer self . activeLock . Unlock ( )
cleanedMountPoint , err := filepath . Abs ( filepath . Clean ( mountpoint ) )
cleanedMountPoint , err := filepath . Abs ( filepath . Clean ( mountpoint ) )
if err != nil {
if err != nil {
return err . Error ( ) , err
return false , err
}
}
// Get the mount information based on the mountpoint argument
mountInfo := self . activeMounts [ cleanedMountPoint ]
mountInfo := self . activeMounts [ cleanedMountPoint ]
if mountInfo == nil || mountInfo . MountPoint != cleanedMountPoint {
return false , fmt . Errorf ( "%s is not mounted" , 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 )
err = fuse . Unmount ( cleanedMountPoint )
if err != nil {
if err != nil {
//TODO: try forceful unmount if normal unmount fails
// TODO(jmozah): try forceful unmount if normal unmount fails
errStr := fmt . Sprintf ( "UnMount error: %v" , err )
return false , err
log . Warn ( errStr )
return errStr , err
}
}
// remove the mount information from the active map
mountInfo . fuseConnection . Close ( )
mountInfo . fuseConnection . Close ( )
//remove the mount information from the active map
delete ( self . activeMounts , cleanedMountPoint )
delete ( self . activeMounts , cleanedMountPoint )
return true , nil
succString := fmt . Sprintf ( "UnMounting %v succeeded" , cleanedMountPoint )
log . Info ( succString )
return succString , nil
}
}
func ( self * SwarmFS ) Listmounts ( ) ( string , error ) {
func ( self * SwarmFS ) Listmounts ( ) [ ] * MountInfo {
self . activeLock . RLock ( )
self . activeLock . RLock ( )
defer self . activeLock . RUnlock ( )
defer self . activeLock . RUnlock ( )
var rows [ ] string
rows := make ( [ ] * MountInfo , 0 , len ( self . activeMounts ) )
for mp := range self . activeMounts {
for _ , mi := range self . activeMounts {
mountInfo := self . activeMounts [ mp ]
rows = append ( rows , mi )
rows = append ( rows , fmt . Sprintf ( "Swarm Root: %s, Mount Point: %s " , mountInfo . manifestHash , mountInfo . mountPoint ) )
}
}
return rows
return strings . Join ( rows , "\n" ) , nil
}
}
func ( self * SwarmFS ) Stop ( ) bool {
func ( self * SwarmFS ) Stop ( ) bool {
for mp := range self . activeMounts {
for mp := range self . activeMounts {
mountInfo := self . activeMounts [ mp ]
mountInfo := self . activeMounts [ mp ]
self . Unmount ( mountInfo . m ountPoint)
self . Unmount ( mountInfo . M ountPoint)
}
}
return true
return true
}
}