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 }