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  }