github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/fuse/config.go (about) 1 // Package fuse is a FUSE filesystem for ProxyFS (an alternative to the Samba-VFS frontend). 2 package fuse 3 4 import ( 5 "fmt" 6 "os" 7 "os/exec" 8 "path" 9 "syscall" 10 "time" 11 12 fuselib "bazil.org/fuse" 13 fusefslib "bazil.org/fuse/fs" 14 15 "github.com/swiftstack/ProxyFS/conf" 16 "github.com/swiftstack/ProxyFS/fs" 17 "github.com/swiftstack/ProxyFS/logger" 18 "github.com/swiftstack/ProxyFS/trackedlock" 19 "github.com/swiftstack/ProxyFS/transitions" 20 ) 21 22 const ( 23 maxRetryCount uint32 = 100 24 retryGap = 100 * time.Millisecond 25 ) 26 27 type volumeStruct struct { 28 volumeName string 29 mountPointName string 30 mounted bool 31 } 32 33 type globalsStruct struct { 34 gate trackedlock.RWMutex // API Requests RLock()/RUnlock() 35 // confMap changes Lock()/Unlock() 36 // Note: fuselib.Unmount() results in an Fsync() call on RootDir 37 // Hence, no current confMap changes currently call Lock() 38 volumeMap map[string]*volumeStruct // key == volumeStruct.volumeName 39 mountPointMap map[string]*volumeStruct // key == volumeStruct.mountPointName 40 } 41 42 var globals globalsStruct 43 44 func init() { 45 transitions.Register("fuse", &globals) 46 } 47 48 func (dummy *globalsStruct) Up(confMap conf.ConfMap) (err error) { 49 globals.volumeMap = make(map[string]*volumeStruct) 50 globals.mountPointMap = make(map[string]*volumeStruct) 51 52 closeGate() // Ensure gate starts out in the Exclusively Locked state 53 54 err = nil 55 return 56 } 57 58 func (dummy *globalsStruct) VolumeGroupCreated(confMap conf.ConfMap, volumeGroupName string, activePeer string, virtualIPAddr string) (err error) { 59 return nil 60 } 61 func (dummy *globalsStruct) VolumeGroupMoved(confMap conf.ConfMap, volumeGroupName string, activePeer string, virtualIPAddr string) (err error) { 62 return nil 63 } 64 func (dummy *globalsStruct) VolumeGroupDestroyed(confMap conf.ConfMap, volumeGroupName string) (err error) { 65 return nil 66 } 67 func (dummy *globalsStruct) VolumeCreated(confMap conf.ConfMap, volumeName string, volumeGroupName string) (err error) { 68 return nil 69 } 70 func (dummy *globalsStruct) VolumeMoved(confMap conf.ConfMap, volumeName string, volumeGroupName string) (err error) { 71 return nil 72 } 73 func (dummy *globalsStruct) VolumeDestroyed(confMap conf.ConfMap, volumeName string) (err error) { 74 return nil 75 } 76 77 func (dummy *globalsStruct) ServeVolume(confMap conf.ConfMap, volumeName string) (err error) { 78 var ( 79 volume *volumeStruct 80 ) 81 82 volume = &volumeStruct{ 83 volumeName: volumeName, 84 mounted: false, 85 } 86 87 volume.mountPointName, err = confMap.FetchOptionValueString("Volume:"+volumeName, "FUSEMountPointName") 88 if nil != err { 89 return 90 } 91 92 globals.volumeMap[volume.volumeName] = volume 93 globals.mountPointMap[volume.mountPointName] = volume 94 95 err = performMount(volume) 96 97 return // return err from performMount() sufficient 98 } 99 100 func (dummy *globalsStruct) UnserveVolume(confMap conf.ConfMap, volumeName string) (err error) { 101 var ( 102 lazyUnmountCmd *exec.Cmd 103 ok bool 104 volume *volumeStruct 105 ) 106 107 volume, ok = globals.volumeMap[volumeName] 108 109 if ok { 110 if volume.mounted { 111 err = fuselib.Unmount(volume.mountPointName) 112 if nil == err { 113 logger.Infof("Unmounted %v", volume.mountPointName) 114 } else { 115 lazyUnmountCmd = exec.Command("fusermount", "-uz", volume.mountPointName) 116 err = lazyUnmountCmd.Run() 117 if nil == err { 118 logger.Infof("Lazily unmounted %v", volume.mountPointName) 119 } else { 120 logger.Infof("Unable to lazily unmount %v - got err == %v", volume.mountPointName, err) 121 } 122 } 123 } 124 125 delete(globals.volumeMap, volume.volumeName) 126 delete(globals.mountPointMap, volume.mountPointName) 127 } 128 129 err = nil 130 return 131 } 132 133 func (dummy *globalsStruct) VolumeToBeUnserved(confMap conf.ConfMap, volumeName string) (err error) { 134 // TODO: Might want to actually FUSE Unmount right here 135 136 err = nil 137 return 138 } 139 140 func (dummy *globalsStruct) SignaledStart(confMap conf.ConfMap) (err error) { 141 closeGate() 142 143 err = nil 144 return 145 } 146 147 func (dummy *globalsStruct) SignaledFinish(confMap conf.ConfMap) (err error) { 148 openGate() 149 150 err = nil 151 return 152 } 153 154 func (dummy *globalsStruct) Down(confMap conf.ConfMap) (err error) { 155 if 0 != len(globals.volumeMap) { 156 err = fmt.Errorf("fuse.Down() called with 0 != len(globals.volumeMap") 157 return 158 } 159 if 0 != len(globals.mountPointMap) { 160 err = fmt.Errorf("fuse.Down() called with 0 != len(globals.mountPointMap") 161 return 162 } 163 164 openGate() // In case we are restarted... Up() expects Gate to initially be open 165 166 err = nil 167 return 168 } 169 170 func fetchInodeDevice(path string) (missing bool, inodeDevice int64, err error) { 171 fi, err := os.Stat(path) 172 if nil != err { 173 if os.IsNotExist(err) { 174 missing = true 175 err = nil 176 } else { 177 err = fmt.Errorf("fetchInodeDevice(%v): os.Stat() failed: %v", path, err) 178 } 179 return 180 } 181 if nil == fi.Sys() { 182 err = fmt.Errorf("fetchInodeDevice(%v): fi.Sys() was nil", path) 183 return 184 } 185 stat, ok := fi.Sys().(*syscall.Stat_t) 186 if !ok { 187 err = fmt.Errorf("fetchInodeDevice(%v): fi.Sys().(*syscall.Stat_t) returned ok == false", path) 188 return 189 } 190 missing = false 191 inodeDevice = int64(stat.Dev) 192 return 193 } 194 195 func performMount(volume *volumeStruct) (err error) { 196 var ( 197 conn *fuselib.Conn 198 curRetryCount uint32 199 lazyUnmountCmd *exec.Cmd 200 missing bool 201 mountPointContainingDirDevice int64 202 mountPointDevice int64 203 mountPointNameBase string 204 volumeHandle fs.VolumeHandle 205 ) 206 207 volume.mounted = false 208 209 missing, mountPointContainingDirDevice, err = fetchInodeDevice(path.Dir(volume.mountPointName)) 210 if nil != err { 211 return 212 } 213 if missing { 214 logger.Infof("Unable to serve %s.FUSEMountPoint == %s (mount point dir's parent does not exist)", volume.volumeName, volume.mountPointName) 215 return 216 } 217 218 missing, mountPointDevice, err = fetchInodeDevice(volume.mountPointName) 219 if nil == err { 220 if missing { 221 logger.Infof("Unable to serve %s.FUSEMountPoint == %s (mount point dir does not exist)", volume.volumeName, volume.mountPointName) 222 return 223 } 224 } 225 226 if (nil != err) || (mountPointDevice != mountPointContainingDirDevice) { 227 // Presumably, the mount point is (still) currently mounted, so attempt to unmount it first 228 229 lazyUnmountCmd = exec.Command("fusermount", "-uz", volume.mountPointName) 230 err = lazyUnmountCmd.Run() 231 if nil != err { 232 return 233 } 234 235 curRetryCount = 0 236 237 for { 238 time.Sleep(retryGap) // Try again in a bit 239 missing, mountPointDevice, err = fetchInodeDevice(volume.mountPointName) 240 if nil == err { 241 if missing { 242 err = fmt.Errorf("Race condition: %s.FUSEMountPoint == %s disappeared [case 1]", volume.volumeName, volume.mountPointName) 243 return 244 } 245 if mountPointDevice == mountPointContainingDirDevice { 246 break 247 } 248 } 249 curRetryCount++ 250 if curRetryCount >= maxRetryCount { 251 err = fmt.Errorf("MaxRetryCount exceeded for %s.FUSEMountPoint == %s [case 1]", volume.volumeName, volume.mountPointName) 252 return 253 } 254 } 255 } 256 257 mountPointNameBase = path.Base(volume.mountPointName) 258 259 conn, err = fuselib.Mount( 260 volume.mountPointName, 261 fuselib.FSName(mountPointNameBase), 262 fuselib.AllowOther(), 263 // OS X specific— 264 fuselib.LocalVolume(), 265 fuselib.VolumeName(mountPointNameBase), 266 fuselib.NoAppleDouble(), 267 fuselib.NoAppleXattr(), 268 ) 269 270 if nil != err { 271 logger.WarnfWithError(err, "Couldn't mount %s.FUSEMountPoint == %s", volume.volumeName, volume.mountPointName) 272 err = nil 273 return 274 } 275 276 volumeHandle, err = fs.FetchVolumeHandleByVolumeName(volume.volumeName) 277 if nil != err { 278 return 279 } 280 281 fs := &ProxyFUSE{volumeHandle: volumeHandle} 282 283 // We synchronize the mounting of the mount point to make sure our FUSE goroutine 284 // has reached the point that it can service requests. 285 // 286 // Otherwise, if proxyfsd is killed after we block on a FUSE request but before our 287 // FUSE goroutine has had a chance to run we end up with an unkillable proxyfsd process. 288 // 289 // This would result in a "proxyfsd <defunct>" process that is only cleared by rebooting 290 // the system. 291 fs.wg.Add(1) 292 293 go func(mountPointName string, conn *fuselib.Conn) { 294 defer conn.Close() 295 fusefslib.Serve(conn, fs) 296 }(volume.mountPointName, conn) 297 298 // Wait for FUSE to mount the file system. The "fs.wg.Done()" is in the 299 // Root() routine. 300 fs.wg.Wait() 301 302 // If we made it to here, all was ok 303 304 logger.Infof("Now serving %s.FUSEMountPoint == %s", volume.volumeName, volume.mountPointName) 305 306 volume.mounted = true 307 308 err = nil 309 return 310 } 311 312 func openGate() { 313 globals.gate.Unlock() 314 } 315 316 func closeGate() { 317 globals.gate.Lock() 318 } 319 320 // Note: The following func's do nothing today. Thus, no "gate" is enforced in this package. 321 // The reason is that as part of the fuselib.Unmount() in UnserveVolume(), a call to 322 // Fsync() will be made. If the closeGate() were honored, the call to Fsync() would 323 // indefinitely block. 324 325 func enterGate() { 326 // globals.gate.RLock() 327 } 328 329 func leaveGate() { 330 // globals.gate.RUnlock() 331 }