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  }