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