github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/fs/resolve_path.go (about)

     1  package fs
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/swiftstack/ProxyFS/blunder"
     8  	"github.com/swiftstack/ProxyFS/dlm"
     9  	"github.com/swiftstack/ProxyFS/inode"
    10  	"github.com/swiftstack/ProxyFS/logger"
    11  	"github.com/swiftstack/ProxyFS/utils"
    12  )
    13  
    14  type resolvePathOption uint32
    15  
    16  const (
    17  	resolvePathFollowDirEntrySymlinks              resolvePathOption = 1 << iota
    18  	resolvePathFollowDirSymlinks                                     // Follow symlink in final pathanme component
    19  	resolvePathCreateMissingPathElements                             // Defaults created DirEntry to be a File
    20  	resolvePathDirEntryInodeMustBeDirectory                          //
    21  	resolvePathDirEntryInodeMustBeFile                               //
    22  	resolvePathDirEntryInodeMustBeSymlink                            //
    23  	resolvePathRequireExclusiveLockOnDirEntryInode                   //
    24  	resolvePathRequireExclusiveLockOnDirInode                        // Presumably only useful if resolvePathRequireExclusiveLockOnDirEntryInode also specified
    25  	resolvePathRequireSharedLockOnDirInode                           // Not valid if resolvePathRequireExclusiveLockOnDirInode also specified
    26  )
    27  
    28  func resolvePathOptionsCheck(optionsRequested resolvePathOption, optionsToCheckFor resolvePathOption) (
    29  	optionsRequestedIncludesCheckFor bool) {
    30  
    31  	optionsRequestedIncludesCheckFor = (optionsToCheckFor == (optionsToCheckFor & optionsRequested))
    32  	return
    33  }
    34  
    35  type heldLocksStruct struct {
    36  	exclusive map[inode.InodeNumber]*dlm.RWLockStruct
    37  	shared    map[inode.InodeNumber]*dlm.RWLockStruct
    38  }
    39  
    40  func newHeldLocks() (heldLocks *heldLocksStruct) {
    41  	heldLocks = &heldLocksStruct{
    42  		exclusive: make(map[inode.InodeNumber]*dlm.RWLockStruct),
    43  		shared:    make(map[inode.InodeNumber]*dlm.RWLockStruct),
    44  	}
    45  	return
    46  }
    47  
    48  func (heldLocks *heldLocksStruct) attemptExclusiveLock(inodeVolumeHandle inode.VolumeHandle, dlmCallerID dlm.CallerID, inodeNumber inode.InodeNumber) (retryRequired bool) {
    49  	var (
    50  		err       error
    51  		inodeLock *dlm.RWLockStruct
    52  		ok        bool
    53  	)
    54  
    55  	_, ok = heldLocks.exclusive[inodeNumber]
    56  	if ok {
    57  		retryRequired = false
    58  		return
    59  	}
    60  
    61  	inodeLock, ok = heldLocks.shared[inodeNumber]
    62  	if ok {
    63  		err = inodeLock.Unlock()
    64  		if nil != err {
    65  			logger.Fatalf("Failure unlocking a held LockID %s: %v", inodeLock.LockID, err)
    66  		}
    67  
    68  		delete(heldLocks.shared, inodeNumber)
    69  	}
    70  
    71  	inodeLock, err = inodeVolumeHandle.AttemptWriteLock(inodeNumber, dlmCallerID)
    72  	if nil != err {
    73  		retryRequired = true
    74  		return
    75  	}
    76  
    77  	heldLocks.exclusive[inodeNumber] = inodeLock
    78  
    79  	retryRequired = false
    80  	return
    81  }
    82  
    83  func (heldLocks *heldLocksStruct) attemptSharedLock(inodeVolumeHandle inode.VolumeHandle, dlmCallerID dlm.CallerID, inodeNumber inode.InodeNumber) (retryRequired bool) {
    84  	var (
    85  		err       error
    86  		inodeLock *dlm.RWLockStruct
    87  		ok        bool
    88  	)
    89  
    90  	_, ok = heldLocks.exclusive[inodeNumber]
    91  	if ok {
    92  		retryRequired = false
    93  		return
    94  	}
    95  	_, ok = heldLocks.shared[inodeNumber]
    96  	if ok {
    97  		retryRequired = false
    98  		return
    99  	}
   100  
   101  	inodeLock, err = inodeVolumeHandle.AttemptReadLock(inodeNumber, dlmCallerID)
   102  	if nil != err {
   103  		retryRequired = true
   104  		return
   105  	}
   106  
   107  	heldLocks.shared[inodeNumber] = inodeLock
   108  
   109  	retryRequired = false
   110  	return
   111  }
   112  
   113  func (heldLocks *heldLocksStruct) unlock(inodeNumber inode.InodeNumber) {
   114  	var (
   115  		err      error
   116  		heldLock *dlm.RWLockStruct
   117  		ok       bool
   118  	)
   119  
   120  	heldLock, ok = heldLocks.exclusive[inodeNumber]
   121  	if ok {
   122  		err = heldLock.Unlock()
   123  		if nil != err {
   124  			logger.Fatalf("Failure unlocking a held LockID %s: %v", heldLock.LockID, err)
   125  		}
   126  		delete(heldLocks.exclusive, inodeNumber)
   127  		return
   128  	}
   129  
   130  	heldLock, ok = heldLocks.shared[inodeNumber]
   131  	if ok {
   132  		err = heldLock.Unlock()
   133  		if nil != err {
   134  			logger.Fatalf("Failure unlocking a held LockID %s: %v", heldLock.LockID, err)
   135  		}
   136  		delete(heldLocks.shared, inodeNumber)
   137  		return
   138  	}
   139  
   140  	logger.Fatalf("Attempt to unlock a non-held Lock on inodeNumber 0x%016X", inodeNumber)
   141  }
   142  
   143  func (heldLocks *heldLocksStruct) free() {
   144  	var (
   145  		err      error
   146  		heldLock *dlm.RWLockStruct
   147  	)
   148  
   149  	for _, heldLock = range heldLocks.exclusive {
   150  		err = heldLock.Unlock()
   151  		if nil != err {
   152  			logger.Fatalf("Failure unlocking a held LockID %s: %v", heldLock.LockID, err)
   153  		}
   154  	}
   155  
   156  	for _, heldLock = range heldLocks.shared {
   157  		err = heldLock.Unlock()
   158  		if nil != err {
   159  			logger.Fatalf("Failure unlocking a held LockID %s: %v", heldLock.LockID, err)
   160  		}
   161  	}
   162  }
   163  
   164  // resolvePath is used to walk the supplied path starting at the supplied DirInode and locate
   165  // the "leaf" {Dir|File}Inode. Various options allow for traversing encountered SymlinkInodes as
   166  // well as optionally creating missing Inodes along the way. The caller is allowed to hold locks
   167  // at entry that will be used (and possibly upgraded from "shared" to "exclusive") as well as
   168  // extended to include any additional locks obtained. All lock requests are attempted so as to
   169  // always avoid deadlock and if attempts fail, retryRequired == TRUE will be returned and the
   170  // caller will be responsible for releasing all held locks, backing off (via a delay), before
   171  // restarting the sequence.
   172  //
   173  // The pattern should look something like this:
   174  //
   175  //     tryLockBackoffContext = &tryLockBackoffContextStruct{}
   176  //
   177  // Restart:
   178  //
   179  //     tryLockBackoffContext.backoff()
   180  //
   181  //     heldLocks = newHeldLocks()
   182  //
   183  //     dirInode, dirEntryInode, retryRequired, err =
   184  //         resolvePath(inode.RootDirInodeNumber, path, heldLocks, resolvePathFollowSymlnks|...)
   185  //
   186  //     if retryRequired {
   187  //         heldLocks.free()
   188  //         goto Restart
   189  //     }
   190  //
   191  //     // Do whatever needed to be done with returned [dirInode and] dirEntryInode
   192  //
   193  //     heldLocks.free()
   194  //
   195  func (vS *volumeStruct) resolvePath(startingInodeNumber inode.InodeNumber, path string, heldLocks *heldLocksStruct, options resolvePathOption) (dirInodeNumber inode.InodeNumber, dirEntryInodeNumber inode.InodeNumber, dirEntryBasename string, dirEntryInodeType inode.InodeType, retryRequired bool, err error) {
   196  	var (
   197  		dirEntryInodeLock                 *dlm.RWLockStruct
   198  		dirEntryInodeLockAlreadyExclusive bool
   199  		dirEntryInodeLockAlreadyHeld      bool
   200  		dirEntryInodeLockAlreadyShared    bool
   201  		dirInodeLock                      *dlm.RWLockStruct
   202  		dirInodeLockAlreadyExclusive      bool
   203  		dirInodeLockAlreadyHeld           bool
   204  		dirInodeLockAlreadyShared         bool
   205  		dlmCallerID                       dlm.CallerID
   206  		followSymlink                     bool
   207  		inodeVolumeHandle                 inode.VolumeHandle
   208  		internalErr                       error
   209  		pathSplit                         []string
   210  		pathSplitPart                     string
   211  		pathSplitPartIndex                int
   212  		symlinkCount                      uint16
   213  		symlinkTarget                     string
   214  	)
   215  
   216  	// Setup default returns
   217  
   218  	dirInodeNumber = inode.InodeNumber(0)
   219  	dirEntryInodeNumber = inode.InodeNumber(0)
   220  	dirEntryBasename = ""
   221  	retryRequired = false
   222  	err = nil
   223  
   224  	// Validate options
   225  
   226  	if resolvePathOptionsCheck(options, resolvePathRequireExclusiveLockOnDirInode) && resolvePathOptionsCheck(options, resolvePathRequireSharedLockOnDirInode) {
   227  		err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,,,options) includes both resolvePathRequireExclusiveLockOnDirInode & resolvePathRequireSharedLockOnDirInode")
   228  		return
   229  	}
   230  
   231  	if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeDirectory) {
   232  		if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeFile) || resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeSymlink) {
   233  			err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,,,options) cannot include more than one {resolvePathDirEntryInodeMustBeDirectory, resolvePathDirEntryInodeMustBeFile, resolvePathDirEntryInodeMustBeSymlink}")
   234  			return
   235  		}
   236  	} else if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeFile) && resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeSymlink) {
   237  		err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,,,options) cannot include more than one {resolvePathDirEntryInodeMustBeDirectory, resolvePathDirEntryInodeMustBeFile, resolvePathDirEntryInodeMustBeSymlink}")
   238  		return
   239  	}
   240  
   241  	// Setup shortcuts/contants
   242  
   243  	dlmCallerID = dlm.GenerateCallerID()
   244  	inodeVolumeHandle = vS.inodeVolumeHandle
   245  
   246  	// Prepare for SymlinkInode-restart handling on canonicalized path
   247  
   248  	symlinkCount = 0
   249  
   250  	pathSplit, internalErr = canonicalizePath(path)
   251  
   252  RestartAfterFollowingSymlink:
   253  
   254  	if nil != internalErr {
   255  		err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,\"%s\",,) invalid", path)
   256  		return
   257  	}
   258  
   259  	// Start loop from a ReadLock on startingInodeNumber
   260  
   261  	dirInodeNumber = inode.InodeNumber(0)
   262  
   263  	dirEntryInodeNumber = startingInodeNumber
   264  
   265  	dirEntryInodeLock, dirEntryInodeLockAlreadyExclusive = heldLocks.exclusive[dirEntryInodeNumber]
   266  	if dirEntryInodeLockAlreadyExclusive {
   267  		dirEntryInodeLockAlreadyHeld = true
   268  		dirEntryInodeLockAlreadyShared = false
   269  	} else {
   270  		dirEntryInodeLock, dirEntryInodeLockAlreadyShared = heldLocks.shared[dirEntryInodeNumber]
   271  		if dirEntryInodeLockAlreadyShared {
   272  			dirEntryInodeLockAlreadyHeld = true
   273  		} else {
   274  			dirEntryInodeLockAlreadyHeld = false
   275  			dirEntryInodeLock, internalErr = inodeVolumeHandle.AttemptReadLock(dirEntryInodeNumber, dlmCallerID)
   276  			if nil != internalErr {
   277  				retryRequired = true
   278  				return
   279  			}
   280  		}
   281  	}
   282  
   283  	if 0 == len(pathSplit) {
   284  		// Special case where path resolves to "/"
   285  		// Note: dirEntryInodeNumber == inode.RootDirInodeNumber
   286  
   287  		// Reject invalid options for the "/" case
   288  
   289  		if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeFile) ||
   290  			resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeSymlink) ||
   291  			resolvePathOptionsCheck(options, resolvePathRequireExclusiveLockOnDirInode) ||
   292  			resolvePathOptionsCheck(options, resolvePathRequireSharedLockOnDirInode) {
   293  			err = blunder.NewError(blunder.InvalidArgError, "resolvePath(inode.RootDirInodeNumber,\"/\",,options) cannot satisfy options: 0x%08X", options)
   294  			return
   295  		}
   296  
   297  		// Attempt to obtain requested lock on "/"
   298  
   299  		if resolvePathOptionsCheck(options, resolvePathRequireExclusiveLockOnDirEntryInode) {
   300  			if !dirEntryInodeLockAlreadyExclusive {
   301  				if dirEntryInodeLockAlreadyShared {
   302  					// Promote heldLocks.shared dirEntryInodeLock to .exclusive
   303  
   304  					internalErr = dirEntryInodeLock.Unlock()
   305  					if nil != internalErr {
   306  						logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   307  					}
   308  
   309  					delete(heldLocks.shared, dirEntryInodeNumber)
   310  					dirEntryInodeLockAlreadyHeld = false
   311  					dirEntryInodeLockAlreadyShared = false
   312  
   313  					dirEntryInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirEntryInodeNumber, dlmCallerID)
   314  					if nil != internalErr {
   315  						// Caller must call heldLocks.free(), "backoff", and retry
   316  
   317  						if !dirInodeLockAlreadyHeld {
   318  							internalErr = dirInodeLock.Unlock()
   319  							if nil != internalErr {
   320  								logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   321  							}
   322  						}
   323  
   324  						retryRequired = true
   325  						return
   326  					}
   327  
   328  					heldLocks.exclusive[dirEntryInodeNumber] = dirEntryInodeLock
   329  					dirEntryInodeLockAlreadyExclusive = true
   330  					dirEntryInodeLockAlreadyHeld = true
   331  				} else {
   332  					// Promote temporary ReadLock dirEntryInodeLock to .exclusive
   333  
   334  					internalErr = dirEntryInodeLock.Unlock()
   335  					if nil != internalErr {
   336  						logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   337  					}
   338  
   339  					dirEntryInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirEntryInodeNumber, dlmCallerID)
   340  					if nil != internalErr {
   341  						// Caller must call heldLocks.free(), "backoff", and retry
   342  
   343  						if !dirInodeLockAlreadyHeld {
   344  							internalErr = dirInodeLock.Unlock()
   345  							if nil != internalErr {
   346  								logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   347  							}
   348  						}
   349  
   350  						retryRequired = true
   351  						return
   352  					}
   353  
   354  					heldLocks.exclusive[dirEntryInodeNumber] = dirEntryInodeLock
   355  					dirEntryInodeLockAlreadyExclusive = true
   356  					dirEntryInodeLockAlreadyHeld = true
   357  				}
   358  			}
   359  		} else {
   360  			if !dirEntryInodeLockAlreadyHeld {
   361  				// Promote temporary ReadLock dirEntryInodeLock to heldLocks.shared
   362  
   363  				heldLocks.shared[dirEntryInodeNumber] = dirEntryInodeLock
   364  				dirEntryInodeLockAlreadyHeld = true
   365  				dirEntryInodeLockAlreadyShared = true
   366  			}
   367  		}
   368  
   369  		return
   370  	}
   371  
   372  	// Now loop for each pathSplit part
   373  
   374  	for pathSplitPartIndex, pathSplitPart = range pathSplit {
   375  		// Shift dirEntryInode to dirInode as we recurse down pathSplit
   376  
   377  		if inode.InodeNumber(0) != dirInodeNumber {
   378  			if !dirInodeLockAlreadyHeld {
   379  				internalErr = dirInodeLock.Unlock()
   380  				if nil != internalErr {
   381  					logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   382  				}
   383  			}
   384  		}
   385  
   386  		dirInodeNumber = dirEntryInodeNumber
   387  		dirInodeLock = dirEntryInodeLock
   388  		dirInodeLockAlreadyExclusive = dirEntryInodeLockAlreadyExclusive
   389  		dirInodeLockAlreadyHeld = dirEntryInodeLockAlreadyHeld
   390  		dirInodeLockAlreadyShared = dirEntryInodeLockAlreadyShared
   391  
   392  		// Lookup dirEntry
   393  
   394  		dirEntryBasename = pathSplitPart // In case this is the last pathSplitPart that needs to be returned
   395  
   396  		dirEntryInodeNumber, internalErr = inodeVolumeHandle.Lookup(dirInodeNumber, pathSplitPart)
   397  
   398  		if nil == internalErr {
   399  			// Lookup() succeeded... ensure we have at least a ReadLock on dirEntryInode
   400  
   401  			dirEntryInodeLock, dirEntryInodeLockAlreadyExclusive = heldLocks.exclusive[dirEntryInodeNumber]
   402  			if dirEntryInodeLockAlreadyExclusive {
   403  				dirEntryInodeLockAlreadyHeld = true
   404  				dirEntryInodeLockAlreadyShared = false
   405  			} else {
   406  				dirEntryInodeLock, dirEntryInodeLockAlreadyShared = heldLocks.shared[dirEntryInodeNumber]
   407  				if dirEntryInodeLockAlreadyShared {
   408  					dirEntryInodeLockAlreadyHeld = true
   409  				} else {
   410  					dirEntryInodeLockAlreadyHeld = false
   411  					dirEntryInodeLock, internalErr = inodeVolumeHandle.AttemptReadLock(dirEntryInodeNumber, dlmCallerID)
   412  					if nil != internalErr {
   413  						// Caller must call heldLocks.free(), "backoff", and retry
   414  						// But first, free locks not recorded in heldLocks (if any)
   415  
   416  						if !dirInodeLockAlreadyHeld {
   417  							internalErr = dirInodeLock.Unlock()
   418  							if nil != internalErr {
   419  								logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   420  							}
   421  						}
   422  
   423  						retryRequired = true
   424  						return
   425  					}
   426  				}
   427  			}
   428  
   429  			// Handle SymlinkInode case if requested
   430  
   431  			dirEntryInodeType, internalErr = inodeVolumeHandle.GetType(dirEntryInodeNumber)
   432  			if nil != internalErr {
   433  				logger.Fatalf("resolvePath(): failed obtaining InodeType for some part of path \"%s\"", path)
   434  			}
   435  			if dirEntryInodeType == inode.SymlinkType {
   436  				if pathSplitPartIndex < (len(pathSplit) - 1) {
   437  					followSymlink = resolvePathOptionsCheck(options, resolvePathFollowDirSymlinks)
   438  				} else { // pathSplitPartIndex == (len(pathSplit) - 1)
   439  					followSymlink = resolvePathOptionsCheck(options, resolvePathFollowDirEntrySymlinks)
   440  				}
   441  
   442  				if followSymlink {
   443  					symlinkTarget, internalErr = inodeVolumeHandle.GetSymlink(dirEntryInodeNumber)
   444  					if nil != internalErr {
   445  						logger.Fatalf("resolvePath(): failure from inode.GetSymlink() on a known SymlinkInode: %v", err)
   446  					}
   447  
   448  					// Free locks not recorded in heldLocks (if any)
   449  
   450  					if !dirEntryInodeLockAlreadyHeld {
   451  						internalErr = dirEntryInodeLock.Unlock()
   452  						if nil != internalErr {
   453  							logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   454  						}
   455  					}
   456  					if !dirInodeLockAlreadyHeld {
   457  						internalErr = dirInodeLock.Unlock()
   458  						if nil != internalErr {
   459  							logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   460  						}
   461  					}
   462  
   463  					// Enforce SymlinkMax setting
   464  
   465  					if 0 != globals.symlinkMax {
   466  						symlinkCount++
   467  
   468  						if symlinkCount > globals.symlinkMax {
   469  							err = blunder.NewError(blunder.TooManySymlinksError, "resolvePath(): exceeded SymlinkMax")
   470  							return
   471  						}
   472  					}
   473  
   474  					// Apply symlinkTarget to path, reCanonicalize resultant path, and restart
   475  
   476  					pathSplit, internalErr = reCanonicalizePathForSymlink(pathSplit, pathSplitPartIndex, symlinkTarget)
   477  					goto RestartAfterFollowingSymlink
   478  				} else {
   479  					// Not following SymlinkInode... so its a failure if not last pathSplitPart
   480  
   481  					if pathSplitPartIndex < (len(pathSplit) - 1) {
   482  						// But first, free locks not recorded in heldLocks (if any)
   483  
   484  						if !dirEntryInodeLockAlreadyHeld {
   485  							internalErr = dirEntryInodeLock.Unlock()
   486  							if nil != internalErr {
   487  								logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   488  							}
   489  						}
   490  						if !dirInodeLockAlreadyHeld {
   491  							internalErr = dirInodeLock.Unlock()
   492  							if nil != internalErr {
   493  								logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   494  							}
   495  						}
   496  
   497  						err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,\"%s\",,) invalid", path)
   498  						return
   499  					} else {
   500  						// Being the last pathSplitPart, ensure caller didn't require it to be a DirInode or FileInode
   501  
   502  						if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeDirectory) {
   503  							if !dirEntryInodeLockAlreadyHeld {
   504  								internalErr = dirEntryInodeLock.Unlock()
   505  								if nil != internalErr {
   506  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   507  								}
   508  							}
   509  							if !dirInodeLockAlreadyHeld {
   510  								internalErr = dirInodeLock.Unlock()
   511  								if nil != internalErr {
   512  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   513  								}
   514  							}
   515  
   516  							err = blunder.NewError(blunder.InvalidArgError,
   517  								"resolvePath(,\"%s\",,) '%s' is not a directory "+
   518  									"stack:\n%s",
   519  								path, pathSplitPart, utils.MyStackTrace())
   520  
   521  							return
   522  						} else if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeFile) {
   523  							if !dirEntryInodeLockAlreadyHeld {
   524  								internalErr = dirEntryInodeLock.Unlock()
   525  								if nil != internalErr {
   526  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   527  								}
   528  							}
   529  							if !dirInodeLockAlreadyHeld {
   530  								internalErr = dirInodeLock.Unlock()
   531  								if nil != internalErr {
   532  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   533  								}
   534  							}
   535  
   536  							err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,\"%s\",,) did not find FileInode", path)
   537  							return
   538  						} else {
   539  							// SymlinkInode is ok
   540  						}
   541  					}
   542  				}
   543  			} else {
   544  				// Not a SymlinkInode... check if it's last pathSplitPart and, if so, of correct InodeType
   545  
   546  				if pathSplitPartIndex == (len(pathSplit) - 1) {
   547  					if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeDirectory) {
   548  						if dirEntryInodeType != inode.DirType {
   549  							if !dirEntryInodeLockAlreadyHeld {
   550  								internalErr = dirEntryInodeLock.Unlock()
   551  								if nil != internalErr {
   552  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   553  								}
   554  							}
   555  							if !dirInodeLockAlreadyHeld {
   556  								internalErr = dirInodeLock.Unlock()
   557  								if nil != internalErr {
   558  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   559  								}
   560  							}
   561  
   562  							err = blunder.NewError(blunder.InvalidArgError,
   563  								"resolvePath(,\"%s\",,) '%s' is not a directory "+
   564  									"stack:\n%s",
   565  								path, pathSplitPart, utils.MyStackTrace())
   566  							return
   567  						}
   568  					} else if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeFile) {
   569  						if dirEntryInodeType != inode.FileType {
   570  							if !dirEntryInodeLockAlreadyHeld {
   571  								internalErr = dirEntryInodeLock.Unlock()
   572  								if nil != internalErr {
   573  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   574  								}
   575  							}
   576  							if !dirInodeLockAlreadyHeld {
   577  								internalErr = dirInodeLock.Unlock()
   578  								if nil != internalErr {
   579  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   580  								}
   581  							}
   582  
   583  							err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,\"%s\",,) did not find FileInode", path)
   584  							return
   585  						}
   586  					} else if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeSymlink) {
   587  						if dirEntryInodeType != inode.SymlinkType {
   588  							if !dirEntryInodeLockAlreadyHeld {
   589  								internalErr = dirEntryInodeLock.Unlock()
   590  								if nil != internalErr {
   591  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   592  								}
   593  							}
   594  							if !dirInodeLockAlreadyHeld {
   595  								internalErr = dirInodeLock.Unlock()
   596  								if nil != internalErr {
   597  									logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   598  								}
   599  							}
   600  
   601  							err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,\"%s\",,) did not find SymlinkInode", path)
   602  							return
   603  						}
   604  					} else {
   605  						// Any InodeType is ok
   606  					}
   607  				}
   608  			}
   609  		} else {
   610  			// Lookup() failed... is resolvePath() asked to create missing Inode?
   611  
   612  			if resolvePathOptionsCheck(options, resolvePathCreateMissingPathElements) {
   613  				// Cannot implicitly create a SymlinkInode for last pathSplitPart
   614  
   615  				if (pathSplitPartIndex == (len(pathSplit) - 1)) && resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeSymlink) {
   616  					if !dirInodeLockAlreadyHeld {
   617  						internalErr = dirInodeLock.Unlock()
   618  						if nil != internalErr {
   619  							logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   620  						}
   621  					}
   622  
   623  					err = blunder.NewError(blunder.InvalidArgError, "resolvePath(,\"%s\",,) did not find SymlinkInode", path)
   624  					return
   625  				}
   626  
   627  				// Must hold exclusive lock to create missing {Dir|File}Inode
   628  
   629  				if !dirInodeLockAlreadyExclusive {
   630  					if dirInodeLockAlreadyShared {
   631  						// Promote heldLocks.shared InodeLock to .exclusive
   632  
   633  						internalErr = dirInodeLock.Unlock()
   634  						if nil != internalErr {
   635  							logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   636  						}
   637  
   638  						delete(heldLocks.shared, dirInodeNumber)
   639  						dirInodeLockAlreadyHeld = false
   640  						dirInodeLockAlreadyShared = false
   641  
   642  						dirInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirInodeNumber, dlmCallerID)
   643  						if nil != internalErr {
   644  							// Caller must call heldLocks.free(), "backoff", and retry
   645  
   646  							retryRequired = true
   647  							return
   648  						}
   649  
   650  						heldLocks.exclusive[dirInodeNumber] = dirInodeLock
   651  						dirInodeLockAlreadyExclusive = true
   652  						dirInodeLockAlreadyHeld = true
   653  					} else {
   654  						// Promote temporary ReadLock to heldLocks.exclusive
   655  
   656  						internalErr = dirInodeLock.Unlock()
   657  						if nil != internalErr {
   658  							logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   659  						}
   660  
   661  						dirInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirInodeNumber, dlmCallerID)
   662  						if nil != internalErr {
   663  							// Caller must call heldLocks.free(), "backoff", and retry
   664  
   665  							retryRequired = true
   666  							return
   667  						}
   668  
   669  						heldLocks.exclusive[dirInodeNumber] = dirInodeLock
   670  						dirInodeLockAlreadyExclusive = true
   671  						dirInodeLockAlreadyHeld = true
   672  					}
   673  				}
   674  
   675  				// Create missing {Dir|File}Inode (cannot implicitly create a SymlinkInode)
   676  
   677  				if (pathSplitPartIndex < (len(pathSplit) - 1)) || resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeDirectory) {
   678  					// Create a DirInode to be inserted
   679  
   680  					dirEntryInodeType = inode.DirType
   681  
   682  					dirEntryInodeNumber, internalErr = inodeVolumeHandle.CreateDir(inode.InodeMode(0777), inode.InodeRootUserID, inode.InodeGroupID(0))
   683  					if nil != internalErr {
   684  						err = blunder.NewError(blunder.PermDeniedError, "resolvePath(): failed to create a DirInode: %v", err)
   685  						return
   686  					}
   687  				} else { // (pathSplitPartIndex == (len(pathSplit) - 1)) && !resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeDirectory)
   688  					if resolvePathOptionsCheck(options, resolvePathDirEntryInodeMustBeSymlink) {
   689  						// Cannot implicitly create a SymlinkInode
   690  
   691  						err = blunder.NewError(blunder.InvalidArgError, "resolvePath(): cannot create a missing SymlinkInode")
   692  						return
   693  					} else {
   694  						// Create a FileInode to be inserted
   695  
   696  						dirEntryInodeType = inode.FileType
   697  
   698  						dirEntryInodeNumber, internalErr = inodeVolumeHandle.CreateFile(inode.InodeMode(0666), inode.InodeRootUserID, inode.InodeGroupID(0))
   699  						if nil != internalErr {
   700  							err = blunder.NewError(blunder.PermDeniedError, "resolvePath(): failed to create a FileInode: %v", err)
   701  							return
   702  						}
   703  					}
   704  				}
   705  
   706  				// Obtain and record an exclusive lock on just created {Dir|File}Inode
   707  
   708  				dirEntryInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirEntryInodeNumber, dlmCallerID)
   709  				if nil != internalErr {
   710  					logger.Fatalf("resolvePath(): failed to exclusively lock just-created Inode 0x%016X: %v", dirEntryInodeNumber, internalErr)
   711  				}
   712  
   713  				heldLocks.exclusive[dirEntryInodeNumber] = dirEntryInodeLock
   714  				dirEntryInodeLockAlreadyExclusive = true
   715  				dirEntryInodeLockAlreadyHeld = true
   716  				dirEntryInodeLockAlreadyShared = false
   717  
   718  				// Now insert created {Dir|File}Inode
   719  
   720  				internalErr = inodeVolumeHandle.Link(dirInodeNumber, pathSplitPart, dirEntryInodeNumber, false)
   721  				if nil != internalErr {
   722  					err = blunder.NewError(blunder.PermDeniedError, "resolvePath(): failed to Link created {Dir|File}Inode into path %s: %v", path, internalErr)
   723  					internalErr = inodeVolumeHandle.Destroy(dirEntryInodeNumber)
   724  					if nil != internalErr {
   725  						logger.Errorf("resolvePath(): failed to Destroy() created {Dir|File}Inode 0x%016X: %v", dirEntryInodeNumber, internalErr)
   726  					}
   727  					return
   728  				}
   729  			} else {
   730  				// Don't create missing Inode... so its a failure
   731  				// But first, free locks not recorded in heldLocks (if any)
   732  
   733  				if !dirInodeLockAlreadyHeld {
   734  					internalErr = dirInodeLock.Unlock()
   735  					if nil != internalErr {
   736  						logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   737  					}
   738  				}
   739  
   740  				err = blunder.NewError(blunder.NotFoundError, "resolvePath(,\"%s\",,) invalid", path)
   741  				return
   742  			}
   743  		}
   744  	}
   745  
   746  	if resolvePathOptionsCheck(options, resolvePathRequireExclusiveLockOnDirEntryInode) {
   747  		if !dirEntryInodeLockAlreadyExclusive {
   748  			if dirEntryInodeLockAlreadyShared {
   749  				// Promote heldLocks.shared dirEntryInodeLock to .exclusive
   750  
   751  				internalErr = dirEntryInodeLock.Unlock()
   752  				if nil != internalErr {
   753  					logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   754  				}
   755  
   756  				delete(heldLocks.shared, dirEntryInodeNumber)
   757  				dirEntryInodeLockAlreadyHeld = false
   758  				dirEntryInodeLockAlreadyShared = false
   759  
   760  				dirEntryInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirEntryInodeNumber, dlmCallerID)
   761  				if nil != internalErr {
   762  					// Caller must call heldLocks.free(), "backoff", and retry
   763  
   764  					if !dirInodeLockAlreadyHeld {
   765  						internalErr = dirInodeLock.Unlock()
   766  						if nil != internalErr {
   767  							logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   768  						}
   769  					}
   770  
   771  					retryRequired = true
   772  					return
   773  				}
   774  
   775  				heldLocks.exclusive[dirEntryInodeNumber] = dirEntryInodeLock
   776  				dirEntryInodeLockAlreadyExclusive = true
   777  				dirEntryInodeLockAlreadyHeld = true
   778  			} else {
   779  				// Promote temporary ReadLock dirEntryInodeLock to .exclusive
   780  
   781  				internalErr = dirEntryInodeLock.Unlock()
   782  				if nil != internalErr {
   783  					logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirEntryInodeLock.LockID, internalErr)
   784  				}
   785  
   786  				dirEntryInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirEntryInodeNumber, dlmCallerID)
   787  				if nil != internalErr {
   788  					// Caller must call heldLocks.free(), "backoff", and retry
   789  
   790  					if !dirInodeLockAlreadyHeld {
   791  						internalErr = dirInodeLock.Unlock()
   792  						if nil != internalErr {
   793  							logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   794  						}
   795  					}
   796  
   797  					retryRequired = true
   798  					return
   799  				}
   800  
   801  				heldLocks.exclusive[dirEntryInodeNumber] = dirEntryInodeLock
   802  				dirEntryInodeLockAlreadyExclusive = true
   803  				dirEntryInodeLockAlreadyHeld = true
   804  			}
   805  		}
   806  	} else {
   807  		if !dirEntryInodeLockAlreadyHeld {
   808  			// Promote temporary ReadLock dirEntryInodeLock to heldLocks.shared
   809  
   810  			heldLocks.shared[dirEntryInodeNumber] = dirEntryInodeLock
   811  			dirEntryInodeLockAlreadyHeld = true
   812  			dirEntryInodeLockAlreadyShared = true
   813  		}
   814  	}
   815  
   816  	if resolvePathOptionsCheck(options, resolvePathRequireExclusiveLockOnDirInode) {
   817  		if !dirInodeLockAlreadyExclusive {
   818  			if dirInodeLockAlreadyShared {
   819  				// Promote heldLocks.shared dirInodeLock to .exclusive
   820  
   821  				internalErr = dirInodeLock.Unlock()
   822  				if nil != internalErr {
   823  					logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   824  				}
   825  
   826  				delete(heldLocks.shared, dirInodeNumber)
   827  				dirInodeLockAlreadyHeld = false
   828  				dirInodeLockAlreadyShared = false
   829  
   830  				dirInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirInodeNumber, dlmCallerID)
   831  				if nil != internalErr {
   832  					// Caller must call heldLocks.free(), "backoff", and retry
   833  
   834  					retryRequired = true
   835  					return
   836  				}
   837  
   838  				heldLocks.exclusive[dirInodeNumber] = dirInodeLock
   839  				dirInodeLockAlreadyExclusive = true
   840  				dirInodeLockAlreadyHeld = true
   841  			} else {
   842  				// Promote temporary ReadLock dirInodeLock to .exclusive
   843  
   844  				internalErr = dirInodeLock.Unlock()
   845  				if nil != internalErr {
   846  					logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   847  				}
   848  
   849  				dirInodeLock, internalErr = inodeVolumeHandle.AttemptWriteLock(dirInodeNumber, dlmCallerID)
   850  				if nil != internalErr {
   851  					// Caller must call heldLocks.free(), "backoff", and retry
   852  
   853  					retryRequired = true
   854  					return
   855  				}
   856  
   857  				heldLocks.exclusive[dirInodeNumber] = dirInodeLock
   858  				dirInodeLockAlreadyExclusive = true
   859  				dirInodeLockAlreadyHeld = true
   860  			}
   861  		}
   862  	} else if resolvePathOptionsCheck(options, resolvePathRequireSharedLockOnDirInode) {
   863  		if !dirInodeLockAlreadyHeld {
   864  			// Promote temporary ReadLock dirInodeLock to .shared
   865  
   866  			heldLocks.shared[dirInodeNumber] = dirInodeLock
   867  			dirInodeLockAlreadyHeld = true
   868  			dirInodeLockAlreadyShared = true
   869  		}
   870  	} else {
   871  		if !dirInodeLockAlreadyHeld {
   872  			// If only temporary ReadLock dirInodeLock is held, release it
   873  
   874  			internalErr = dirInodeLock.Unlock()
   875  			if nil != internalErr {
   876  				logger.Fatalf("resolvePath(): failed unlocking held LockID %s: %v", dirInodeLock.LockID, internalErr)
   877  			}
   878  		}
   879  	}
   880  
   881  	return
   882  }
   883  
   884  func canonicalizePath(path string) (canonicalizedPathSplit []string, err error) {
   885  	var (
   886  		canonicalizedPathSplitLen int
   887  		pathSplit                 []string
   888  		pathSplitElement          string
   889  	)
   890  
   891  	pathSplit = strings.Split(path, "/")
   892  
   893  	canonicalizedPathSplit = make([]string, 0, len(pathSplit))
   894  
   895  	for _, pathSplitElement = range pathSplit {
   896  		switch pathSplitElement {
   897  		case "":
   898  			// drop pathSplitElement
   899  		case ".":
   900  			// drop pathSplitElement
   901  		case "..":
   902  			// backup one pathSplitElement
   903  			canonicalizedPathSplitLen = len(canonicalizedPathSplit)
   904  			if 0 == canonicalizedPathSplitLen {
   905  				err = fmt.Errorf("\"..\" in path stepped beyond start of path")
   906  				return
   907  			}
   908  			canonicalizedPathSplit = canonicalizedPathSplit[:canonicalizedPathSplitLen-1]
   909  		default:
   910  			// append pathSplitElement
   911  			canonicalizedPathSplit = append(canonicalizedPathSplit, pathSplitElement)
   912  		}
   913  	}
   914  
   915  	err = nil
   916  	return
   917  }
   918  
   919  func reCanonicalizePathForSymlink(canonicalizedPathSplit []string, symlinkIndex int, symlinkTarget string) (reCanonicalizedPathSplit []string, err error) {
   920  	var (
   921  		updatedPath              string
   922  		updatedPathAfterSymlink  string
   923  		updatedPathBeforeSymlink string
   924  	)
   925  
   926  	if (0 == symlinkIndex) || strings.HasPrefix(symlinkTarget, "/") {
   927  		updatedPathBeforeSymlink = ""
   928  	} else {
   929  		updatedPathBeforeSymlink = strings.Join(canonicalizedPathSplit[:symlinkIndex], "/")
   930  	}
   931  
   932  	if len(canonicalizedPathSplit) == (symlinkIndex - 1) {
   933  		updatedPathAfterSymlink = ""
   934  	} else {
   935  		updatedPathAfterSymlink = strings.Join(canonicalizedPathSplit[symlinkIndex+1:], "/")
   936  	}
   937  
   938  	updatedPath = updatedPathBeforeSymlink + "/" + symlinkTarget + "/" + updatedPathAfterSymlink
   939  
   940  	reCanonicalizedPathSplit, err = canonicalizePath(updatedPath)
   941  
   942  	return
   943  }
   944  
   945  // canonicalizePathAndLocateLeafDirInode performs what canonicalizePath() does above but, in addition,
   946  // locates the index in the resultant canonicalizedPathSplit where an existing directory resides. Note
   947  // that no DLM Locks should be held at invocation as this func will be performing any necessary retries
   948  // and would have no way of backing out of a caller's existing DLM locks.
   949  //
   950  // Returns:
   951  //   If canonicalPath is empty or invalid, err will be non-nil
   952  //   If path-located Inode exists,
   953  //     If path-located Inode is     a DirInode, dirInodeIndex == len(canonicalizedPathSplit) - 1
   954  //     If path-located Inode is not a DirInode, dirInodeIndex == len(canonicalizedPathSplit) - 2
   955  //   If path-located Inode does not exist,      dirInodeIndex == len(canonicalizedPathSplit) - 2
   956  //
   957  // Note 1: A dirInodeIndex == -1 is certainly possible and is, indeed, the expected result when
   958  //         parsing a path that directory refers to a Swift Container (i.e. root-level subdirectory).
   959  //
   960  // Note 2: No symbolic links will be followed. This is not a problem given that all we are really
   961  //         trying to indicate is if we know the path would resolve to a directory or not. It is
   962  //         certainly possible that the leaf element of the supplied path is a SymlinkInode pointing
   963  //         at a DirInode, but in that case it's still ok to begin searching from the DirInode
   964  //         containing the SymlinkInode (as in the case of MiddlewareGetContainer()'s use case).
   965  //
   966  // Note that a dirInodeIndex == -1 is possible
   967  //
   968  func (vS *volumeStruct) canonicalizePathAndLocateLeafDirInode(path string) (canonicalizedPathSplit []string, dirInodeIndex int, err error) {
   969  	var (
   970  		dirEntryInodeType     inode.InodeType
   971  		heldLocks             *heldLocksStruct
   972  		retryRequired         bool
   973  		tryLockBackoffContext *tryLockBackoffContextStruct
   974  	)
   975  
   976  	canonicalizedPathSplit, err = canonicalizePath(path)
   977  	if nil != err {
   978  		return
   979  	}
   980  	if 0 == len(canonicalizedPathSplit) {
   981  		err = fmt.Errorf("Canonically empty path \"%s\" not allowed", path)
   982  		return
   983  	}
   984  
   985  	tryLockBackoffContext = &tryLockBackoffContextStruct{}
   986  
   987  Restart:
   988  
   989  	tryLockBackoffContext.backoff()
   990  
   991  	heldLocks = newHeldLocks()
   992  
   993  	_, _, _, dirEntryInodeType, retryRequired, err =
   994  		vS.resolvePath(
   995  			inode.RootDirInodeNumber,
   996  			strings.Join(canonicalizedPathSplit, "/"),
   997  			heldLocks,
   998  			0)
   999  	if nil != err {
  1000  		heldLocks.free()
  1001  		dirInodeIndex = len(canonicalizedPathSplit) - 2
  1002  		err = nil
  1003  		return
  1004  	}
  1005  	if retryRequired {
  1006  		heldLocks.free()
  1007  		goto Restart
  1008  	}
  1009  
  1010  	heldLocks.free()
  1011  
  1012  	if inode.DirType == dirEntryInodeType {
  1013  		dirInodeIndex = len(canonicalizedPathSplit) - 1
  1014  	} else {
  1015  		dirInodeIndex = len(canonicalizedPathSplit) - 2
  1016  	}
  1017  
  1018  	err = nil
  1019  	return
  1020  }