github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/swarm/fuse/swarmfs_unix.go (about) 1 // Copyright 2017 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum 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/SmartMeshFoundation/Spectrum/common" 33 "github.com/SmartMeshFoundation/Spectrum/log" 34 "github.com/SmartMeshFoundation/Spectrum/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 func NewMountInfo(mhash, mpoint string, sapi *api.Api) *MountInfo { 63 newMountInfo := &MountInfo{ 64 MountPoint: mpoint, 65 StartManifest: mhash, 66 LatestManifest: mhash, 67 rootDir: nil, 68 fuseConnection: nil, 69 swarmApi: sapi, 70 lock: &sync.RWMutex{}, 71 } 72 return newMountInfo 73 } 74 75 func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { 76 77 if mountpoint == "" { 78 return nil, errEmptyMountPoint 79 } 80 cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) 81 if err != nil { 82 return nil, err 83 } 84 85 self.swarmFsLock.Lock() 86 defer self.swarmFsLock.Unlock() 87 88 noOfActiveMounts := len(self.activeMounts) 89 if noOfActiveMounts >= maxFuseMounts { 90 return nil, errMaxMountCount 91 } 92 93 if _, ok := self.activeMounts[cleanedMountPoint]; ok { 94 return nil, errAlreadyMounted 95 } 96 97 log.Info(fmt.Sprintf("Attempting to mount %s ", cleanedMountPoint)) 98 _, manifestEntryMap, err := self.swarmApi.BuildDirectoryTree(mhash, true) 99 if err != nil { 100 return nil, err 101 } 102 103 mi := NewMountInfo(mhash, cleanedMountPoint, self.swarmApi) 104 105 dirTree := map[string]*SwarmDir{} 106 rootDir := NewSwarmDir("/", mi) 107 dirTree["/"] = rootDir 108 mi.rootDir = rootDir 109 110 for suffix, entry := range manifestEntryMap { 111 key := common.Hex2Bytes(entry.Hash) 112 fullpath := "/" + suffix 113 basepath := filepath.Dir(fullpath) 114 115 parentDir := rootDir 116 dirUntilNow := "" 117 paths := strings.Split(basepath, "/") 118 for i := range paths { 119 if paths[i] != "" { 120 thisDir := paths[i] 121 dirUntilNow = dirUntilNow + "/" + thisDir 122 123 if _, ok := dirTree[dirUntilNow]; !ok { 124 dirTree[dirUntilNow] = NewSwarmDir(dirUntilNow, mi) 125 parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow]) 126 parentDir = dirTree[dirUntilNow] 127 128 } else { 129 parentDir = dirTree[dirUntilNow] 130 } 131 132 } 133 } 134 thisFile := NewSwarmFile(basepath, filepath.Base(fullpath), mi) 135 thisFile.key = key 136 137 parentDir.files = append(parentDir.files, thisFile) 138 } 139 140 fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash)) 141 if isFUSEUnsupportedError(err) { 142 log.Warn("Fuse not installed", "mountpoint", cleanedMountPoint, "err", err) 143 return nil, err 144 } else if err != nil { 145 fuse.Unmount(cleanedMountPoint) 146 log.Warn("Error mounting swarm manifest", "mountpoint", cleanedMountPoint, "err", err) 147 return nil, err 148 } 149 mi.fuseConnection = fconn 150 151 serverr := make(chan error, 1) 152 go func() { 153 log.Info(fmt.Sprintf("Serving %s at %s", mhash, cleanedMountPoint)) 154 filesys := &SwarmRoot{root: rootDir} 155 if err := fs.Serve(fconn, filesys); err != nil { 156 log.Warn(fmt.Sprintf("Could not Serve SwarmFileSystem error: %v", err)) 157 serverr <- err 158 } 159 160 }() 161 162 // Check if the mount process has an error to report. 163 select { 164 case <-time.After(mountTimeout): 165 fuse.Unmount(cleanedMountPoint) 166 return nil, errMountTimeout 167 168 case err := <-serverr: 169 fuse.Unmount(cleanedMountPoint) 170 log.Warn("Error serving swarm FUSE FS", "mountpoint", cleanedMountPoint, "err", err) 171 return nil, err 172 173 case <-fconn.Ready: 174 log.Info("Now serving swarm FUSE FS", "manifest", mhash, "mountpoint", cleanedMountPoint) 175 } 176 177 self.activeMounts[cleanedMountPoint] = mi 178 return mi, nil 179 } 180 181 func (self *SwarmFS) Unmount(mountpoint string) (*MountInfo, error) { 182 183 self.swarmFsLock.Lock() 184 defer self.swarmFsLock.Unlock() 185 186 cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) 187 if err != nil { 188 return nil, err 189 } 190 191 mountInfo := self.activeMounts[cleanedMountPoint] 192 193 if mountInfo == nil || mountInfo.MountPoint != cleanedMountPoint { 194 return nil, fmt.Errorf("%s is not mounted", cleanedMountPoint) 195 } 196 err = fuse.Unmount(cleanedMountPoint) 197 if err != nil { 198 err1 := externalUnmount(cleanedMountPoint) 199 if err1 != nil { 200 errStr := fmt.Sprintf("UnMount error: %v", err) 201 log.Warn(errStr) 202 return nil, err1 203 } 204 } 205 206 mountInfo.fuseConnection.Close() 207 delete(self.activeMounts, cleanedMountPoint) 208 209 succString := fmt.Sprintf("UnMounting %v succeeded", cleanedMountPoint) 210 log.Info(succString) 211 212 return mountInfo, nil 213 } 214 215 func (self *SwarmFS) Listmounts() []*MountInfo { 216 self.swarmFsLock.RLock() 217 defer self.swarmFsLock.RUnlock() 218 219 rows := make([]*MountInfo, 0, len(self.activeMounts)) 220 for _, mi := range self.activeMounts { 221 rows = append(rows, mi) 222 } 223 return rows 224 } 225 226 func (self *SwarmFS) Stop() bool { 227 for mp := range self.activeMounts { 228 mountInfo := self.activeMounts[mp] 229 self.Unmount(mountInfo.MountPoint) 230 } 231 return true 232 }