github.com/memikequinn/go-ethereum@v1.6.6-0.20170621145815-58a1e13e6dd7/swarm/fuse/swarmfs_unix.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // +build linux darwin freebsd 18 19 package fuse 20 21 import ( 22 "errors" 23 "fmt" 24 "os" 25 "path/filepath" 26 "strings" 27 "sync" 28 "time" 29 30 "bazil.org/fuse" 31 "bazil.org/fuse/fs" 32 "github.com/ethereum/go-ethereum/common" 33 "github.com/ethereum/go-ethereum/log" 34 "github.com/ethereum/go-ethereum/swarm/api" 35 ) 36 37 var ( 38 errEmptyMountPoint = errors.New("need non-empty mount point") 39 errMaxMountCount = errors.New("max FUSE mount count reached") 40 errMountTimeout = errors.New("mount timeout") 41 errAlreadyMounted = errors.New("mount point is already serving") 42 ) 43 44 func isFUSEUnsupportedError(err error) bool { 45 if perr, ok := err.(*os.PathError); ok { 46 return perr.Op == "open" && perr.Path == "/dev/fuse" 47 } 48 return err == fuse.ErrOSXFUSENotFound 49 } 50 51 // information about every active mount 52 type MountInfo struct { 53 MountPoint string 54 StartManifest string 55 LatestManifest string 56 rootDir *SwarmDir 57 fuseConnection *fuse.Conn 58 swarmApi *api.Api 59 lock *sync.RWMutex 60 } 61 62 // Inode numbers need to be unique, they are used for caching inside fuse 63 func newInode() uint64 { 64 inodeLock.Lock() 65 defer inodeLock.Unlock() 66 inode += 1 67 return inode 68 } 69 70 func NewMountInfo(mhash, mpoint string, sapi *api.Api) *MountInfo { 71 newMountInfo := &MountInfo{ 72 MountPoint: mpoint, 73 StartManifest: mhash, 74 LatestManifest: mhash, 75 rootDir: nil, 76 fuseConnection: nil, 77 swarmApi: sapi, 78 lock: &sync.RWMutex{}, 79 } 80 return newMountInfo 81 } 82 83 func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { 84 85 if mountpoint == "" { 86 return nil, errEmptyMountPoint 87 } 88 cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) 89 if err != nil { 90 return nil, err 91 } 92 93 self.swarmFsLock.Lock() 94 defer self.swarmFsLock.Unlock() 95 96 noOfActiveMounts := len(self.activeMounts) 97 if noOfActiveMounts >= maxFuseMounts { 98 return nil, errMaxMountCount 99 } 100 101 if _, ok := self.activeMounts[cleanedMountPoint]; ok { 102 return nil, errAlreadyMounted 103 } 104 105 log.Info(fmt.Sprintf("Attempting to mount %s ", cleanedMountPoint)) 106 key, manifestEntryMap, err := self.swarmApi.BuildDirectoryTree(mhash, true) 107 if err != nil { 108 return nil, err 109 } 110 111 mi := NewMountInfo(mhash, cleanedMountPoint, self.swarmApi) 112 113 dirTree := map[string]*SwarmDir{} 114 rootDir := NewSwarmDir("/", mi) 115 dirTree["/"] = rootDir 116 mi.rootDir = rootDir 117 118 for suffix, entry := range manifestEntryMap { 119 120 key = common.Hex2Bytes(entry.Hash) 121 fullpath := "/" + suffix 122 basepath := filepath.Dir(fullpath) 123 124 parentDir := rootDir 125 dirUntilNow := "" 126 paths := strings.Split(basepath, "/") 127 for i := range paths { 128 if paths[i] != "" { 129 thisDir := paths[i] 130 dirUntilNow = dirUntilNow + "/" + thisDir 131 132 if _, ok := dirTree[dirUntilNow]; !ok { 133 dirTree[dirUntilNow] = NewSwarmDir(dirUntilNow, mi) 134 parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow]) 135 parentDir = dirTree[dirUntilNow] 136 137 } else { 138 parentDir = dirTree[dirUntilNow] 139 } 140 141 } 142 } 143 thisFile := NewSwarmFile(basepath, filepath.Base(fullpath), mi) 144 thisFile.key = key 145 146 parentDir.files = append(parentDir.files, thisFile) 147 } 148 149 fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash)) 150 if isFUSEUnsupportedError(err) { 151 log.Warn("Fuse not installed", "mountpoint", cleanedMountPoint, "err", err) 152 return nil, err 153 } else if err != nil { 154 fuse.Unmount(cleanedMountPoint) 155 log.Warn("Error mounting swarm manifest", "mountpoint", cleanedMountPoint, "err", err) 156 return nil, err 157 } 158 mi.fuseConnection = fconn 159 160 serverr := make(chan error, 1) 161 go func() { 162 log.Info(fmt.Sprintf("Serving %s at %s", mhash, cleanedMountPoint)) 163 filesys := &SwarmRoot{root: rootDir} 164 if err := fs.Serve(fconn, filesys); err != nil { 165 log.Warn(fmt.Sprintf("Could not Serve SwarmFileSystem error: %v", err)) 166 serverr <- err 167 } 168 169 }() 170 171 // Check if the mount process has an error to report. 172 select { 173 case <-time.After(mountTimeout): 174 fuse.Unmount(cleanedMountPoint) 175 return nil, errMountTimeout 176 177 case err := <-serverr: 178 fuse.Unmount(cleanedMountPoint) 179 log.Warn("Error serving swarm FUSE FS", "mountpoint", cleanedMountPoint, "err", err) 180 return nil, err 181 182 case <-fconn.Ready: 183 log.Info("Now serving swarm FUSE FS", "manifest", mhash, "mountpoint", cleanedMountPoint) 184 } 185 186 self.activeMounts[cleanedMountPoint] = mi 187 return mi, nil 188 } 189 190 func (self *SwarmFS) Unmount(mountpoint string) (*MountInfo, error) { 191 192 self.swarmFsLock.Lock() 193 defer self.swarmFsLock.Unlock() 194 195 cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) 196 if err != nil { 197 return nil, err 198 } 199 200 mountInfo := self.activeMounts[cleanedMountPoint] 201 202 if mountInfo == nil || mountInfo.MountPoint != cleanedMountPoint { 203 return nil, fmt.Errorf("%s is not mounted", cleanedMountPoint) 204 } 205 err = fuse.Unmount(cleanedMountPoint) 206 if err != nil { 207 err1 := externalUnmount(cleanedMountPoint) 208 if err1 != nil { 209 errStr := fmt.Sprintf("UnMount error: %v", err) 210 log.Warn(errStr) 211 return nil, err1 212 } 213 } 214 215 mountInfo.fuseConnection.Close() 216 delete(self.activeMounts, cleanedMountPoint) 217 218 succString := fmt.Sprintf("UnMounting %v succeeded", cleanedMountPoint) 219 log.Info(succString) 220 221 return mountInfo, nil 222 } 223 224 func (self *SwarmFS) Listmounts() []*MountInfo { 225 self.swarmFsLock.RLock() 226 defer self.swarmFsLock.RUnlock() 227 228 rows := make([]*MountInfo, 0, len(self.activeMounts)) 229 for _, mi := range self.activeMounts { 230 rows = append(rows, mi) 231 } 232 return rows 233 } 234 235 func (self *SwarmFS) Stop() bool { 236 for mp := range self.activeMounts { 237 mountInfo := self.activeMounts[mp] 238 self.Unmount(mountInfo.MountPoint) 239 } 240 return true 241 }