github.com/alanchchen/go-ethereum@v1.6.6-0.20170601190819-6171d01b1195/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 "bazil.org/fuse" 23 "bazil.org/fuse/fs" 24 "errors" 25 "fmt" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/log" 28 "github.com/ethereum/go-ethereum/swarm/api" 29 "os" 30 "path/filepath" 31 "strings" 32 "sync" 33 "time" 34 ) 35 36 var ( 37 errEmptyMountPoint = errors.New("need non-empty mount point") 38 errMaxMountCount = errors.New("max FUSE mount count reached") 39 errMountTimeout = errors.New("mount timeout") 40 errAlreadyMounted = errors.New("mount point is already serving") 41 ) 42 43 func isFUSEUnsupportedError(err error) bool { 44 if perr, ok := err.(*os.PathError); ok { 45 return perr.Op == "open" && perr.Path == "/dev/fuse" 46 } 47 return err == fuse.ErrOSXFUSENotFound 48 } 49 50 // information about every active mount 51 type MountInfo struct { 52 MountPoint string 53 StartManifest string 54 LatestManifest string 55 rootDir *SwarmDir 56 fuseConnection *fuse.Conn 57 swarmApi *api.Api 58 lock *sync.RWMutex 59 } 60 61 // Inode numbers need to be unique, they are used for caching inside fuse 62 func newInode() uint64 { 63 inodeLock.Lock() 64 defer inodeLock.Unlock() 65 inode += 1 66 return inode 67 } 68 69 func NewMountInfo(mhash, mpoint string, sapi *api.Api) *MountInfo { 70 newMountInfo := &MountInfo{ 71 MountPoint: mpoint, 72 StartManifest: mhash, 73 LatestManifest: mhash, 74 rootDir: nil, 75 fuseConnection: nil, 76 swarmApi: sapi, 77 lock: &sync.RWMutex{}, 78 } 79 return newMountInfo 80 } 81 82 func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { 83 84 if mountpoint == "" { 85 return nil, errEmptyMountPoint 86 } 87 cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) 88 if err != nil { 89 return nil, err 90 } 91 92 self.swarmFsLock.Lock() 93 defer self.swarmFsLock.Unlock() 94 95 noOfActiveMounts := len(self.activeMounts) 96 if noOfActiveMounts >= maxFuseMounts { 97 return nil, errMaxMountCount 98 } 99 100 if _, ok := self.activeMounts[cleanedMountPoint]; ok { 101 return nil, errAlreadyMounted 102 } 103 104 log.Info(fmt.Sprintf("Attempting to mount %s ", cleanedMountPoint)) 105 key, manifestEntryMap, err := self.swarmApi.BuildDirectoryTree(mhash, true) 106 if err != nil { 107 return nil, err 108 } 109 110 mi := NewMountInfo(mhash, cleanedMountPoint, self.swarmApi) 111 112 dirTree := map[string]*SwarmDir{} 113 rootDir := NewSwarmDir("/", mi) 114 dirTree["/"] = rootDir 115 mi.rootDir = rootDir 116 117 for suffix, entry := range manifestEntryMap { 118 119 key = common.Hex2Bytes(entry.Hash) 120 fullpath := "/" + suffix 121 basepath := filepath.Dir(fullpath) 122 123 parentDir := rootDir 124 dirUntilNow := "" 125 paths := strings.Split(basepath, "/") 126 for i := range paths { 127 if paths[i] != "" { 128 thisDir := paths[i] 129 dirUntilNow = dirUntilNow + "/" + thisDir 130 131 if _, ok := dirTree[dirUntilNow]; !ok { 132 dirTree[dirUntilNow] = NewSwarmDir(dirUntilNow, mi) 133 parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow]) 134 parentDir = dirTree[dirUntilNow] 135 136 } else { 137 parentDir = dirTree[dirUntilNow] 138 } 139 140 } 141 } 142 thisFile := NewSwarmFile(basepath, filepath.Base(fullpath), mi) 143 thisFile.key = key 144 145 parentDir.files = append(parentDir.files, thisFile) 146 } 147 148 fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash)) 149 if isFUSEUnsupportedError(err) { 150 log.Warn("Fuse not installed", "mountpoint", cleanedMountPoint, "err", err) 151 return nil, err 152 } else if err != nil { 153 fuse.Unmount(cleanedMountPoint) 154 log.Warn("Error mounting swarm manifest", "mountpoint", cleanedMountPoint, "err", err) 155 return nil, err 156 } 157 mi.fuseConnection = fconn 158 159 serverr := make(chan error, 1) 160 go func() { 161 log.Info(fmt.Sprintf("Serving %s at %s", mhash, cleanedMountPoint)) 162 filesys := &SwarmRoot{root: rootDir} 163 if err := fs.Serve(fconn, filesys); err != nil { 164 log.Warn(fmt.Sprintf("Could not Serve SwarmFileSystem error: %v", err)) 165 serverr <- err 166 } 167 168 }() 169 170 // Check if the mount process has an error to report. 171 select { 172 case <-time.After(mountTimeout): 173 fuse.Unmount(cleanedMountPoint) 174 return nil, errMountTimeout 175 176 case err := <-serverr: 177 fuse.Unmount(cleanedMountPoint) 178 log.Warn("Error serving swarm FUSE FS", "mountpoint", cleanedMountPoint, "err", err) 179 return nil, err 180 181 case <-fconn.Ready: 182 log.Info("Now serving swarm FUSE FS", "manifest", mhash, "mountpoint", cleanedMountPoint) 183 } 184 185 self.activeMounts[cleanedMountPoint] = mi 186 return mi, nil 187 } 188 189 func (self *SwarmFS) Unmount(mountpoint string) (*MountInfo, error) { 190 191 self.swarmFsLock.Lock() 192 defer self.swarmFsLock.Unlock() 193 194 cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) 195 if err != nil { 196 return nil, err 197 } 198 199 mountInfo := self.activeMounts[cleanedMountPoint] 200 201 if mountInfo == nil || mountInfo.MountPoint != cleanedMountPoint { 202 return nil, fmt.Errorf("%s is not mounted", cleanedMountPoint) 203 } 204 err = fuse.Unmount(cleanedMountPoint) 205 if err != nil { 206 err1 := externalUnMount(cleanedMountPoint) 207 if err1 != nil { 208 errStr := fmt.Sprintf("UnMount error: %v", err) 209 log.Warn(errStr) 210 return nil, err1 211 } 212 } 213 214 mountInfo.fuseConnection.Close() 215 delete(self.activeMounts, cleanedMountPoint) 216 217 succString := fmt.Sprintf("UnMounting %v succeeded", cleanedMountPoint) 218 log.Info(succString) 219 220 return mountInfo, nil 221 } 222 223 func (self *SwarmFS) Listmounts() []*MountInfo { 224 self.swarmFsLock.RLock() 225 defer self.swarmFsLock.RUnlock() 226 227 rows := make([]*MountInfo, 0, len(self.activeMounts)) 228 for _, mi := range self.activeMounts { 229 rows = append(rows, mi) 230 } 231 return rows 232 } 233 234 func (self *SwarmFS) Stop() bool { 235 for mp := range self.activeMounts { 236 mountInfo := self.activeMounts[mp] 237 self.Unmount(mountInfo.MountPoint) 238 } 239 return true 240 }