github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/fs/treewalk.go (about) 1 package fs 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "regexp" 10 "strconv" 11 "sync" 12 "time" 13 14 "github.com/swiftstack/sortedmap" 15 16 "github.com/swiftstack/ProxyFS/blunder" 17 "github.com/swiftstack/ProxyFS/dlm" 18 "github.com/swiftstack/ProxyFS/headhunter" 19 "github.com/swiftstack/ProxyFS/inode" 20 "github.com/swiftstack/ProxyFS/logger" 21 "github.com/swiftstack/ProxyFS/swiftclient" 22 ) 23 24 const ( 25 jobBPTreeMaxKeysPerNode = uint64(100) 26 jobBPTreeEvictLowLimit = uint64(90) 27 jobBPTreeEvictHighLimit = uint64(100) 28 29 validateVolumeInodeParallelism = uint64(100) 30 validateVolumeCalculateLinkCountParallelism = uint64(50) 31 validateVolumeFixLinkCountParallelism = uint64(50) 32 validateVolumeIncrementByteCountsParallelism = uint64(100) 33 34 scrubVolumeInodeParallelism = uint64(100) 35 36 invalidLinkCount = uint64(0xFFFFFFFFFFFFFFFF) // Indicates Inode to be removed 37 38 lostAndFoundDirName = ".Lost+Found" 39 ) 40 41 type jobStruct struct { 42 sync.Mutex 43 globalWaitGroup sync.WaitGroup 44 jobType string 45 volumeName string 46 active bool 47 stopFlag bool 48 err []string 49 info []string 50 volume *volumeStruct 51 bpTreeCache sortedmap.BPlusTreeCache 52 bpTreeFile *os.File 53 bpTreeFileNextOffset uint64 54 parallelismChan chan struct{} 55 parallelismChanSize uint64 56 inodeVolumeHandle inode.VolumeHandle 57 headhunterVolumeHandle headhunter.VolumeHandle 58 inodeBPTree sortedmap.BPlusTree // Maps Inode# to LinkCount 59 childrenWaitGroup sync.WaitGroup 60 } 61 62 type validateVolumeStruct struct { 63 jobStruct 64 objectBPTree sortedmap.BPlusTree // Maps Object# to ByteCount 65 lostAndFoundDirInodeNumber inode.InodeNumber 66 accountName string 67 checkpointContainerName string 68 } 69 70 type scrubVolumeStruct struct { 71 jobStruct 72 } 73 74 func (jS *jobStruct) Active() (active bool) { 75 active = jS.active 76 return 77 } 78 79 func (jS *jobStruct) Wait() { 80 jS.globalWaitGroup.Wait() 81 } 82 83 func (jS *jobStruct) Cancel() { 84 jS.stopFlag = true 85 jS.Wait() 86 } 87 88 func (jS *jobStruct) Error() (err []string) { 89 var errString string 90 91 jS.Lock() 92 93 err = make([]string, 0, len(jS.err)) 94 for _, errString = range jS.err { 95 err = append(err, errString) 96 } 97 98 jS.Unlock() 99 100 return 101 } 102 103 func (jS *jobStruct) Info() (info []string) { 104 var infoString string 105 106 jS.Lock() 107 108 info = make([]string, 0, len(jS.info)) 109 for _, infoString = range jS.info { 110 info = append(info, infoString) 111 } 112 113 jS.Unlock() 114 115 return 116 } 117 118 func (jS *jobStruct) DumpKey(key sortedmap.Key) (keyAsString string, err error) { 119 keyAsString = fmt.Sprintf("0x%016X", key.(uint64)) 120 err = nil 121 return 122 } 123 124 func (jS *jobStruct) DumpValue(value sortedmap.Value) (valueAsString string, err error) { 125 valueAsString = fmt.Sprintf("0x%016X", value.(uint64)) 126 err = nil 127 return 128 } 129 130 func (jS *jobStruct) GetNode(objectNumber uint64, objectOffset uint64, objectLength uint64) (nodeByteSlice []byte, err error) { 131 jS.Lock() 132 defer jS.Unlock() 133 nodeByteSlice = make([]byte, 0, objectLength) 134 _, err = jS.bpTreeFile.Seek(io.SeekStart, int(objectOffset)) 135 if nil != err { 136 return 137 } 138 _, err = io.ReadFull(jS.bpTreeFile, nodeByteSlice) 139 return 140 } 141 142 func (jS *jobStruct) PutNode(nodeByteSlice []byte) (objectNumber uint64, objectOffset uint64, err error) { 143 jS.Lock() 144 defer jS.Unlock() 145 _, err = jS.bpTreeFile.Seek(io.SeekEnd, int(0)) 146 if nil != err { 147 return 148 } 149 _, err = jS.bpTreeFile.WriteAt(nodeByteSlice, int64(jS.bpTreeFileNextOffset)) 150 if nil != err { 151 return 152 } 153 objectNumber = 0 154 objectOffset = jS.bpTreeFileNextOffset 155 jS.bpTreeFileNextOffset += uint64(len(nodeByteSlice)) 156 return 157 } 158 159 func (jS *jobStruct) DiscardNode(objectNumber uint64, objectOffset uint64, objectLength uint64) (err error) { 160 // Short-lived vVS.bpTreeFile... no compaction/garbage-collection needed 161 err = nil 162 return 163 } 164 165 func (jS *jobStruct) PackKey(key sortedmap.Key) (packedKey []byte, err error) { 166 var ( 167 u64 uint64 168 ) 169 170 u64 = key.(uint64) 171 172 packedKey = make([]byte, 8) 173 174 binary.LittleEndian.PutUint64(packedKey, u64) 175 176 err = nil 177 return 178 } 179 180 func (jS *jobStruct) UnpackKey(payloadData []byte) (key sortedmap.Key, bytesConsumed uint64, err error) { 181 if 8 > len(payloadData) { 182 err = fmt.Errorf("fs.validateVolumeStruct.UnpackKey() called with insufficiently sized payloadData (%v) - should be at least 8", len(payloadData)) 183 return 184 } 185 186 key = binary.LittleEndian.Uint64(payloadData[:8]) 187 bytesConsumed = 8 188 189 err = nil 190 return 191 } 192 193 func (jS *jobStruct) PackValue(value sortedmap.Value) (packedValue []byte, err error) { 194 var ( 195 u64 uint64 196 ) 197 198 u64 = value.(uint64) 199 200 packedValue = make([]byte, 8) 201 202 binary.LittleEndian.PutUint64(packedValue, u64) 203 204 err = nil 205 return 206 } 207 208 func (jS *jobStruct) UnpackValue(payloadData []byte) (value sortedmap.Value, bytesConsumed uint64, err error) { 209 if 8 > len(payloadData) { 210 err = fmt.Errorf("fs.validateVolumeStruct.UnpackValue() called with insufficiently sized payloadData (%v) - should be at least 8", len(payloadData)) 211 return 212 } 213 214 value = binary.LittleEndian.Uint64(payloadData[:8]) 215 bytesConsumed = 8 216 217 err = nil 218 return 219 } 220 221 func (jS *jobStruct) jobStartParallelism(parallelismChanSize uint64) { 222 var ( 223 i uint64 224 ) 225 226 jS.parallelismChan = make(chan struct{}, parallelismChanSize) 227 jS.parallelismChanSize = parallelismChanSize 228 229 for i = uint64(0); i < parallelismChanSize; i++ { 230 jS.jobReleaseParallelism() 231 } 232 } 233 234 func (jS *jobStruct) jobGrabParallelism() { 235 _ = <-jS.parallelismChan 236 } 237 238 func (jS *jobStruct) jobReleaseParallelism() { 239 jS.parallelismChan <- struct{}{} 240 } 241 242 func (jS *jobStruct) jobEndParallelism() { 243 var ( 244 i uint64 245 ) 246 247 for i = uint64(0); i < jS.parallelismChanSize; i++ { 248 jS.jobGrabParallelism() 249 } 250 } 251 252 func (jS *jobStruct) jobLogErr(formatString string, args ...interface{}) { 253 jS.Lock() 254 jS.jobLogErrWhileLocked(formatString, args...) 255 jS.Unlock() 256 } 257 258 func (jS *jobStruct) jobLogErrWhileLocked(formatString string, args ...interface{}) { 259 var ( 260 suffix string 261 ) 262 263 suffix = fmt.Sprintf(formatString, args...) 264 265 logger.Errorf("%v[%v] %v", jS.jobType, jS.volumeName, suffix) 266 267 jS.err = append(jS.err, fmt.Sprintf("%v %v", time.Now().Format(time.RFC3339), suffix)) 268 } 269 270 func (jS *jobStruct) jobLogInfo(formatString string, args ...interface{}) { 271 jS.Lock() 272 jS.jobLogInfoWhileLocked(formatString, args...) 273 jS.Unlock() 274 } 275 276 func (jS *jobStruct) jobLogInfoWhileLocked(formatString string, args ...interface{}) { 277 var ( 278 suffix string 279 ) 280 281 suffix = fmt.Sprintf(formatString, args...) 282 283 logger.Infof("%v[%v] %v", jS.jobType, jS.volumeName, suffix) 284 285 jS.info = append(jS.info, fmt.Sprintf("%v %v", time.Now().Format(time.RFC3339), suffix)) 286 } 287 288 func (vVS *validateVolumeStruct) validateVolumeInode(inodeNumber uint64) { 289 var ( 290 err error 291 ) 292 293 defer vVS.childrenWaitGroup.Done() 294 295 defer vVS.jobReleaseParallelism() 296 297 err = vVS.inodeVolumeHandle.Validate(inode.InodeNumber(inodeNumber), false) 298 if nil != err { 299 vVS.jobLogInfo("Got inode.Validate(0x%016X) failure: %v", inodeNumber, err) 300 301 // Ultimately we will to delete or fix corrupt inode, but only after 302 // fixing validation errors. 303 // 304 // vVS.Lock() 305 // defer vVS.Unlock() 306 // 307 // vVS.jobLogInfoWhileLocked("Got inode.Validate(0x%016X) failure: %v", inodeNumber, err) 308 // ok, err = vVS.inodeBPTree.Put(inodeNumber, invalidLinkCount) 309 // if nil != err { 310 // vVS.jobLogErrWhileLocked("Got vVS.inodeBPTree.Put(0x%016X, invalidLinkCount) failure: %v", inodeNumber, err) 311 // return 312 // } 313 // if !ok { 314 // vVS.jobLogErrWhileLocked("Got vVS.inodeBPTree.Put(0x%016X, invalidLinkCount) !ok", inodeNumber) 315 // return 316 // } 317 } 318 } 319 320 func (vVS *validateVolumeStruct) validateVolumeIncrementCalculatedLinkCountWhileLocked(inodeNumber uint64) (ok bool) { 321 var ( 322 err error 323 linkCount uint64 324 value sortedmap.Value 325 ) 326 327 value, ok, err = vVS.inodeBPTree.GetByKey(inodeNumber) 328 if nil != err { 329 vVS.jobLogErrWhileLocked("Got vVS.inodeBPTree.GetByKey(0x%016X) failure: %v", inodeNumber, err) 330 ok = false 331 return 332 } 333 if !ok { 334 vVS.jobLogErrWhileLocked("Got vVS.inodeBPTree.GetByKey(0x%016X) !ok", inodeNumber) 335 ok = false 336 return 337 } 338 linkCount = value.(uint64) + 1 339 ok, err = vVS.inodeBPTree.PatchByKey(inodeNumber, linkCount) 340 if nil != err { 341 vVS.jobLogErrWhileLocked("Got vVS.inodeBPTree.PatchByKey(0x%016X,) failure: %v", inodeNumber, err) 342 ok = false 343 return 344 } 345 if !ok { 346 vVS.jobLogErrWhileLocked("Got vVS.inodeBPTree.PatchByKey(0x%016X,) !ok", inodeNumber) 347 ok = false 348 return 349 } 350 351 ok = true 352 353 return 354 } 355 356 func (vVS *validateVolumeStruct) validateVolumeCalculateLinkCount(parentDirInodeNumber uint64, dirInodeNumber uint64) { 357 var ( 358 dirEntrySlice []inode.DirEntry 359 dotDotWasSeen bool 360 dotWasSeen bool 361 err error 362 inodeNumber uint64 363 inodeType inode.InodeType 364 moreEntries bool 365 ok bool 366 prevReturnedAsString string 367 ) 368 369 defer vVS.childrenWaitGroup.Done() 370 371 defer vVS.jobReleaseParallelism() 372 373 prevReturnedAsString = "" 374 375 dotWasSeen = false 376 dotDotWasSeen = false 377 378 forLabel: 379 for { 380 if vVS.stopFlag { 381 return 382 } 383 384 dirEntrySlice, moreEntries, err = vVS.inodeVolumeHandle.ReadDir(inode.InodeNumber(dirInodeNumber), 1, 0, prevReturnedAsString) 385 if nil != err { 386 vVS.jobLogErr("Got inode.ReadDir(0x%016X,1,0,\"%v\") failure: %v", dirInodeNumber, prevReturnedAsString, err) 387 return 388 } 389 390 if 0 == len(dirEntrySlice) { 391 break forLabel 392 } else if 1 < len(dirEntrySlice) { 393 vVS.jobLogErr("Got too many DirEntry's from inode.ReadDir(0x%016X,1,0,\"%v\")", dirInodeNumber, prevReturnedAsString) 394 return 395 } 396 397 // Increment LinkCount for dirEntrySlice[0]'s InodeNumber 398 399 inodeNumber = uint64(dirEntrySlice[0].InodeNumber) 400 prevReturnedAsString = dirEntrySlice[0].Basename 401 402 vVS.Lock() 403 404 if ("." == prevReturnedAsString) && (inodeNumber != dirInodeNumber) { 405 _, err = vVS.inodeVolumeHandle.Unlink(inode.InodeNumber(dirInodeNumber), ".", true) 406 if nil != err { 407 vVS.jobLogErrWhileLocked("Got inode.Unlink(0x%016X,\".\",true) failure: %v", dirInodeNumber, err) 408 vVS.Unlock() 409 return 410 } 411 err = vVS.inodeVolumeHandle.Link(inode.InodeNumber(dirInodeNumber), ".", inode.InodeNumber(dirInodeNumber), true) 412 if nil != err { 413 vVS.jobLogErrWhileLocked("Got inode.Link(0x%016X,\".\",0x%016X,true) failure: %v", dirInodeNumber, dirInodeNumber, err) 414 vVS.Unlock() 415 return 416 } 417 418 vVS.jobLogInfoWhileLocked("\".\" dirEntry in dirInodeNumber 0x%016X was 0x%016X...fixed to point to dirInodeNumber", dirInodeNumber, inodeNumber) 419 420 inodeNumber = dirInodeNumber 421 } else if (".." == prevReturnedAsString) && (inodeNumber != parentDirInodeNumber) { 422 _, err = vVS.inodeVolumeHandle.Unlink(inode.InodeNumber(dirInodeNumber), "..", true) 423 if nil != err { 424 vVS.jobLogErrWhileLocked("Got inode.Unlink(0x%016X,\"..\",true) failure: %v", dirInodeNumber, err) 425 vVS.Unlock() 426 return 427 } 428 err = vVS.inodeVolumeHandle.Link(inode.InodeNumber(dirInodeNumber), "..", inode.InodeNumber(parentDirInodeNumber), true) 429 if nil != err { 430 vVS.jobLogErrWhileLocked("Got inode.Link(0x%016X,\"..\",0x%016X,true) failure: %v", dirInodeNumber, parentDirInodeNumber, err) 431 vVS.Unlock() 432 return 433 } 434 435 vVS.jobLogInfoWhileLocked("\"..\" dirEntry in dirInodeNumber 0x%016X was 0x%016X...fixed to point to parentDirInodeNumber (0x%016X)", dirInodeNumber, inodeNumber, parentDirInodeNumber) 436 437 inodeNumber = parentDirInodeNumber 438 } 439 440 ok = vVS.validateVolumeIncrementCalculatedLinkCountWhileLocked(inodeNumber) 441 if !ok { 442 vVS.Unlock() 443 return 444 } 445 446 vVS.Unlock() 447 448 // Recurse into sub-directories 449 450 if "." == prevReturnedAsString { 451 dotWasSeen = true 452 } else if ".." == prevReturnedAsString { 453 dotDotWasSeen = true 454 } else { 455 inodeType, err = vVS.inodeVolumeHandle.GetType(inode.InodeNumber(inodeNumber)) 456 if nil != err { 457 vVS.jobLogErr("Got inode.GetType(0x%016X) failure: %v", inodeNumber, err) 458 return 459 } 460 461 if inode.DirType == inodeType { 462 vVS.jobGrabParallelism() 463 vVS.childrenWaitGroup.Add(1) 464 go vVS.validateVolumeCalculateLinkCount(dirInodeNumber, inodeNumber) 465 } 466 } 467 468 if !moreEntries { 469 break forLabel 470 } 471 } 472 473 if !dotWasSeen { 474 vVS.Lock() 475 err = vVS.inodeVolumeHandle.Link(inode.InodeNumber(dirInodeNumber), ".", inode.InodeNumber(dirInodeNumber), true) 476 if nil != err { 477 vVS.jobLogErrWhileLocked("Got inode.Link(0x%016X,\".\",0x%016X,true) failure: %v", dirInodeNumber, dirInodeNumber, err) 478 vVS.Unlock() 479 return 480 } 481 ok = vVS.validateVolumeIncrementCalculatedLinkCountWhileLocked(dirInodeNumber) 482 if !ok { 483 vVS.Unlock() 484 return 485 } 486 vVS.jobLogInfoWhileLocked("\"..\" dirEntry in dirInodeNumber 0x%016X was missing...fixed to point to dirInodeNumber", dirInodeNumber) 487 vVS.Unlock() 488 return 489 } 490 491 if !dotDotWasSeen { 492 vVS.Lock() 493 err = vVS.inodeVolumeHandle.Link(inode.InodeNumber(dirInodeNumber), "..", inode.InodeNumber(parentDirInodeNumber), true) 494 if nil != err { 495 vVS.jobLogErrWhileLocked("Got inode.Link(0x%016X,\"..\",0x%016X,true) failure: %v", dirInodeNumber, parentDirInodeNumber, err) 496 vVS.Unlock() 497 return 498 } 499 ok = vVS.validateVolumeIncrementCalculatedLinkCountWhileLocked(parentDirInodeNumber) 500 if !ok { 501 vVS.Unlock() 502 return 503 } 504 vVS.jobLogInfoWhileLocked("\"..\" dirEntry in dirInodeNumber 0x%016X was missing...fixed to point to parentDirInodeNumber (0x%016X)", dirInodeNumber, parentDirInodeNumber) 505 vVS.Unlock() 506 return 507 } 508 } 509 510 func (vVS *validateVolumeStruct) validateVolumeFixLinkCount(inodeNumber uint64, linkCountComputed uint64) { 511 var ( 512 err error 513 linkCountInInode uint64 514 ) 515 516 defer vVS.childrenWaitGroup.Done() 517 518 defer vVS.jobReleaseParallelism() 519 520 linkCountInInode, err = vVS.inodeVolumeHandle.GetLinkCount(inode.InodeNumber(inodeNumber)) 521 if nil != err { 522 vVS.jobLogErr("Got inode.GetLinkCount(0x%016X) failure: %v", inodeNumber, err) 523 return 524 } 525 526 if linkCountComputed != linkCountInInode { 527 err = vVS.inodeVolumeHandle.SetLinkCount(inode.InodeNumber(inodeNumber), linkCountComputed) 528 if nil == err { 529 vVS.jobLogInfo("Corrected LinkCount in Inode# 0x%016X from 0x%016X to 0x%016X", inodeNumber, linkCountInInode, linkCountComputed) 530 } else { 531 vVS.jobLogErr("Got inode.SetLinkCount(0x%016X,) failure: %v", inodeNumber, err) 532 return 533 } 534 } 535 } 536 537 func (vVS *validateVolumeStruct) validateVolumeIncrementByteCounts(inodeNumber uint64) { 538 var ( 539 err error 540 layoutReport sortedmap.LayoutReport 541 objectBytes uint64 542 objectNumber uint64 543 ok bool 544 value sortedmap.Value 545 ) 546 547 defer vVS.childrenWaitGroup.Done() 548 549 defer vVS.jobReleaseParallelism() 550 551 if vVS.stopFlag { 552 return 553 } 554 555 layoutReport, err = vVS.inodeVolumeHandle.FetchLayoutReport(inode.InodeNumber(inodeNumber)) 556 if nil != err { 557 vVS.jobLogErr("Got vVS.inodeVolumeHandle.FetchLayoutReport(0x%016X) failure: %v", inodeNumber, err) 558 return 559 } 560 561 vVS.Lock() 562 defer vVS.Unlock() 563 564 for objectNumber, objectBytes = range layoutReport { 565 value, ok, err = vVS.objectBPTree.GetByKey(objectNumber) 566 if nil != err { 567 vVS.jobLogErrWhileLocked("Got vVS.objectBPTree.GetByKey() failure: %v", err) 568 return 569 } 570 571 if ok { 572 objectBytes += value.(uint64) 573 574 ok, err = vVS.objectBPTree.PatchByKey(objectNumber, objectBytes) 575 if nil != err { 576 vVS.jobLogErrWhileLocked("Got vVS.objectBPTree.PatchByKey() failure: %v", err) 577 return 578 } 579 if !ok { 580 vVS.jobLogErrWhileLocked("Got vVS.objectBPTree.PatchByKey(0) !ok") 581 return 582 } 583 } else { 584 ok, err = vVS.objectBPTree.Put(objectNumber, objectBytes) 585 if nil != err { 586 vVS.jobLogErrWhileLocked("Got vVS.objectBPTree.Put() failure: %v", err) 587 return 588 } 589 if !ok { 590 vVS.jobLogErrWhileLocked("Got vVS.objectBPTree.Put() !ok") 591 return 592 } 593 } 594 } 595 } 596 597 func (vVS *validateVolumeStruct) validateVolume() { 598 var ( 599 checkpointContainerObjectList []string 600 checkpointContainerObjectName string 601 checkpointContainerObjectNumber uint64 602 discrepencies uint64 603 err error 604 headhunterLayoutReport sortedmap.LayoutReport 605 inodeCount int 606 inodeIndex uint64 607 inodeNumber uint64 608 inodeType inode.InodeType 609 key sortedmap.Key 610 linkCountComputed uint64 611 moreEntries bool 612 objectIndex uint64 613 objectNumber uint64 614 ok bool 615 toDestroyInodeNumber inode.InodeNumber 616 validObjectNameRE = regexp.MustCompile("\\A[0-9a-fA-F]+\\z") 617 value sortedmap.Value 618 ) 619 620 vVS.jobLogInfo("FSCK job initiated") 621 622 defer func(vVS *validateVolumeStruct) { 623 if vVS.stopFlag { 624 vVS.jobLogInfo("FSCK job stopped") 625 } else if 0 == len(vVS.err) { 626 vVS.jobLogInfo("FSCK job completed without error") 627 } else if 1 == len(vVS.err) { 628 vVS.jobLogInfo("FSCK job exited with one error") 629 } else { 630 vVS.jobLogInfo("FSCK job exited with errors") 631 } 632 }(vVS) 633 634 defer func(vVS *validateVolumeStruct) { 635 vVS.active = false 636 }(vVS) 637 638 defer vVS.globalWaitGroup.Done() 639 640 // Find specified volume 641 642 globals.Lock() 643 644 vVS.volume, ok = globals.volumeMap[vVS.volumeName] 645 if !ok { 646 globals.Unlock() 647 vVS.jobLogErr("Couldn't find fs.volumeStruct") 648 return 649 } 650 651 globals.Unlock() 652 653 vVS.inodeVolumeHandle, err = inode.FetchVolumeHandle(vVS.volumeName) 654 if nil != err { 655 vVS.jobLogErr("Couldn't find inode.VolumeHandle") 656 return 657 } 658 659 vVS.headhunterVolumeHandle, err = headhunter.FetchVolumeHandle(vVS.volumeName) 660 if nil != err { 661 vVS.jobLogErr("Couldn't find headhunter.VolumeHandle") 662 return 663 } 664 665 vVS.volume.jobRWMutex.Lock() 666 defer vVS.volume.jobRWMutex.Unlock() 667 668 // Flush all File Inodes currently in flight 669 670 vVS.volume.untrackInFlightFileInodeDataAll() 671 672 vVS.jobLogInfo("Completed flush of all inflight File Inode write traffic") 673 674 // Do a checkpoint before actual FSCK work 675 676 err = vVS.headhunterVolumeHandle.DoCheckpoint() 677 if nil != err { 678 vVS.jobLogErr("Got headhunter.DoCheckpoint failure: %v", err) 679 return 680 } 681 682 vVS.jobLogInfo("Completed checkpoint prior to FSCK work") 683 684 // Setup B+Tree to hold arbitrarily sized map[uint64]uint64 (i.e. beyond what will fit in memoory) 685 686 vVS.bpTreeFile, err = ioutil.TempFile("", "ProxyFS_VaidateVolume_") 687 if nil != err { 688 vVS.jobLogErr("Got ioutil.TempFile() failure: %v", err) 689 return 690 } 691 defer func(vVS *validateVolumeStruct) { 692 var ( 693 bpTreeFileName string 694 ) 695 696 bpTreeFileName = vVS.bpTreeFile.Name() 697 _ = vVS.bpTreeFile.Close() 698 _ = os.Remove(bpTreeFileName) 699 }(vVS) 700 701 vVS.bpTreeFileNextOffset = 0 702 703 vVS.bpTreeCache = sortedmap.NewBPlusTreeCache(jobBPTreeEvictLowLimit, jobBPTreeEvictHighLimit) 704 705 // Validate all Inodes in InodeRec table in headhunter 706 707 vVS.inodeBPTree = sortedmap.NewBPlusTree(jobBPTreeMaxKeysPerNode, sortedmap.CompareUint64, vVS, vVS.bpTreeCache) 708 defer func(vVS *validateVolumeStruct) { 709 var err error 710 711 err = vVS.inodeBPTree.Discard() 712 if nil != err { 713 vVS.jobLogErr("Got vVS.inodeBPTree.Discard() failure: %v", err) 714 } 715 }(vVS) 716 717 vVS.jobLogInfo("Beginning enumeration of inodes to be validated") 718 719 inodeIndex = 0 720 721 for { 722 if vVS.stopFlag { 723 return 724 } 725 726 inodeNumber, ok, err = vVS.headhunterVolumeHandle.IndexedInodeNumber(inodeIndex) 727 if nil != err { 728 vVS.jobLogErr("Got headhunter.IndexedInodeNumber(0x%016X) failure: %v", inodeIndex, err) 729 return 730 } 731 if !ok { 732 break 733 } 734 735 ok, err = vVS.inodeBPTree.Put(inodeNumber, uint64(0)) // Initial Linkount is 0 736 if nil != err { 737 vVS.jobLogErr("Got vVS.inodeBPTree.Put(0x%016X, 0) failure: %v", inodeNumber, err) 738 return 739 } 740 if !ok { 741 vVS.jobLogErr("Got vVS.inodeBPTree.Put(0x%016X, 0) !ok", inodeNumber) 742 return 743 } 744 745 inodeIndex++ 746 } 747 748 if vVS.stopFlag || (0 < len(vVS.err)) { 749 return 750 } 751 752 vVS.jobLogInfo("Completed enumeration of inodes to be validated") 753 754 vVS.jobLogInfo("Beginning validation of inodes") 755 756 inodeCount, err = vVS.inodeBPTree.Len() 757 if nil != err { 758 vVS.jobLogErr("Got vVS.inodeBPTree.Len() failure: %v", err) 759 return 760 } 761 762 vVS.jobStartParallelism(validateVolumeInodeParallelism) 763 764 for inodeIndex = uint64(0); inodeIndex < uint64(inodeCount); inodeIndex++ { 765 if vVS.stopFlag { 766 vVS.childrenWaitGroup.Wait() 767 vVS.jobEndParallelism() 768 return 769 } 770 771 key, _, ok, err = vVS.inodeBPTree.GetByIndex(int(inodeIndex)) 772 if nil != err { 773 vVS.jobLogErr("Got vVS.inodeBPTree.GetByIndex(0x%016X) failure: %v", inodeIndex, err) 774 vVS.childrenWaitGroup.Wait() 775 vVS.jobEndParallelism() 776 return 777 } 778 if !ok { 779 vVS.jobLogErr("Got vVS.inodeBPTree.GetByIndex(0x%016X) !ok", inodeIndex) 780 vVS.childrenWaitGroup.Wait() 781 vVS.jobEndParallelism() 782 return 783 } 784 785 inodeNumber = key.(uint64) 786 787 vVS.jobGrabParallelism() 788 vVS.childrenWaitGroup.Add(1) 789 go vVS.validateVolumeInode(inodeNumber) 790 } 791 792 vVS.childrenWaitGroup.Wait() 793 794 vVS.jobEndParallelism() 795 796 if vVS.stopFlag || (0 < len(vVS.err)) { 797 return 798 } 799 800 // Scan inodeBPTree looking for invalidated Inodes 801 802 inodeIndex = 0 803 804 for { 805 key, value, ok, err = vVS.inodeBPTree.GetByIndex(int(inodeIndex)) 806 if nil != err { 807 vVS.jobLogErr("Got inodeBPTree.GetByIndex(0x%016X) failure: %v", inodeIndex, err) 808 return 809 } 810 811 if !ok { 812 break 813 } 814 815 inodeNumber = key.(uint64) 816 linkCountComputed = value.(uint64) 817 818 if invalidLinkCount == linkCountComputed { 819 err = vVS.headhunterVolumeHandle.DeleteInodeRec(inodeNumber) 820 if nil != err { 821 vVS.jobLogErr("Got headhunter.DeleteInodeRec(0x%016X) failure: %v", inodeNumber, err) 822 return 823 } 824 ok, err = vVS.inodeBPTree.DeleteByIndex(int(inodeIndex)) 825 if nil != err { 826 vVS.jobLogErr("Got inodeBPTree.DeleteByIndex(0x%016X) [inodeNumber == 0x%16X] failure: %v", inodeIndex, inodeNumber, err) 827 return 828 } 829 if !ok { 830 vVS.jobLogErr("Got inodeBPTree.DeleteByIndex(0x%016X) [inodeNumber == 0x%16X] !ok", inodeIndex, inodeNumber) 831 return 832 } 833 vVS.jobLogInfo("Removing inodeNumber 0x%016X due to validation failure", inodeNumber) 834 } else { 835 inodeIndex++ 836 } 837 } 838 839 if vVS.stopFlag || (0 < len(vVS.err)) { 840 return 841 } 842 843 vVS.jobLogInfo("Completed validation of inodes") 844 845 // TreeWalk computing LinkCounts for all inodeNumbers 846 847 _, ok, err = vVS.inodeBPTree.GetByKey(uint64(inode.RootDirInodeNumber)) 848 if nil != err { 849 vVS.jobLogErr("Got vVS.inodeBPTree.GetByKey(RootDirInodeNumber) failure: %v", err) 850 return 851 } 852 if !ok { 853 vVS.jobLogErr("Got vVS.inodeBPTree.GetByKey(RootDirInodeNumber) !ok") 854 return 855 } 856 857 vVS.jobStartParallelism(validateVolumeCalculateLinkCountParallelism) 858 859 vVS.jobGrabParallelism() 860 vVS.childrenWaitGroup.Add(1) 861 go vVS.validateVolumeCalculateLinkCount(uint64(inode.RootDirInodeNumber), uint64(inode.RootDirInodeNumber)) 862 863 vVS.childrenWaitGroup.Wait() 864 865 vVS.jobEndParallelism() 866 867 if vVS.stopFlag || (0 < len(vVS.err)) { 868 return 869 } 870 871 vVS.jobLogInfo("Completed treewalk before populating /%v/", lostAndFoundDirName) 872 873 // Establish that lostAndFoundDirName exists 874 875 vVS.lostAndFoundDirInodeNumber, err = vVS.inodeVolumeHandle.Lookup(inode.RootDirInodeNumber, lostAndFoundDirName) 876 if nil == err { 877 // Found it - make sure it is a directory 878 879 inodeType, err = vVS.inodeVolumeHandle.GetType(vVS.lostAndFoundDirInodeNumber) 880 if nil != err { 881 vVS.jobLogErr("Got inode.GetType(vVS.lostAndFoundDirNumber==0x%016X) failure: %v", vVS.lostAndFoundDirInodeNumber, err) 882 return 883 } 884 if inode.DirType != inodeType { 885 vVS.jobLogErr("Got inode.GetType(vVS.lostAndFoundDirNumber==0x%016X) non-DirType", vVS.lostAndFoundDirInodeNumber) 886 return 887 } 888 889 vVS.jobLogInfo("Found pre-existing /%v/", lostAndFoundDirName) 890 } else { 891 if blunder.Is(err, blunder.NotFoundError) { 892 // Create it 893 894 vVS.lostAndFoundDirInodeNumber, err = vVS.inodeVolumeHandle.CreateDir(inode.PosixModePerm, 0, 0) 895 if nil != err { 896 vVS.jobLogErr("Got inode.CreateDir() failure: %v", err) 897 return 898 } 899 err = vVS.inodeVolumeHandle.Link(inode.RootDirInodeNumber, lostAndFoundDirName, vVS.lostAndFoundDirInodeNumber, false) 900 if nil != err { 901 vVS.jobLogErr("Got inode.Link(inode.RootDirInodeNumber, lostAndFoundDirName, vVS.lostAndFoundDirInodeNumber, false) failure: %v", err) 902 err = vVS.inodeVolumeHandle.Destroy(vVS.lostAndFoundDirInodeNumber) 903 if nil != err { 904 vVS.jobLogErr("Got inode.Destroy(vVS.lostAndFoundDirInodeNumber) failure: %v", err) 905 } 906 return 907 } 908 909 ok, err = vVS.inodeBPTree.Put(uint64(vVS.lostAndFoundDirInodeNumber), uint64(2)) // /<lostAndFoundDirName> as well as /<lostAndFoundDirName>/. 910 if nil != err { 911 vVS.jobLogErr("Got vVS.inodeBPTree.Put(vVS.lostAndFoundDirInodeNumber, 1) failure: %v", err) 912 vVS.Unlock() 913 return 914 } 915 if !ok { 916 vVS.jobLogErr("Got vVS.inodeBPTree.Put(vVS.lostAndFoundDirInodeNumber, 1) !ok") 917 vVS.Unlock() 918 return 919 } 920 921 vVS.jobLogInfo("Created /%v/", lostAndFoundDirName) 922 } else { 923 vVS.jobLogErr("Got inode.Lookup(inode.RootDirInodeNumber, lostAndFoundDirName) failure: %v", err) 924 return 925 } 926 } 927 928 // TODO: Scan B+Tree placing top-most orphan DirInodes as elements of vVS.lostAndFoundDirInodeNumber 929 930 // Re-compute LinkCounts 931 932 inodeCount, err = vVS.inodeBPTree.Len() 933 if nil != err { 934 vVS.jobLogErr("Got vVS.inodeBPTree.Len() failure: %v", err) 935 return 936 } 937 938 for inodeIndex = uint64(0); inodeIndex < uint64(inodeCount); inodeIndex++ { 939 if vVS.stopFlag { 940 return 941 } 942 943 ok, err = vVS.inodeBPTree.PatchByIndex(int(inodeIndex), uint64(0)) 944 if nil != err { 945 vVS.jobLogErr("Got vVS.inodeBPTree.PatchByIndex(0x%016X, 0) failure: %v", inodeIndex, err) 946 return 947 } 948 if !ok { 949 vVS.jobLogErr("Got vVS.inodeBPTree.PatchByIndex(0x%016X, 0) !ok", inodeIndex) 950 return 951 } 952 } 953 954 vVS.jobStartParallelism(validateVolumeCalculateLinkCountParallelism) 955 956 vVS.jobGrabParallelism() 957 vVS.childrenWaitGroup.Add(1) 958 go vVS.validateVolumeCalculateLinkCount(uint64(inode.RootDirInodeNumber), uint64(inode.RootDirInodeNumber)) 959 960 vVS.childrenWaitGroup.Wait() 961 962 vVS.jobEndParallelism() 963 964 if vVS.stopFlag || (0 < len(vVS.err)) { 965 return 966 } 967 968 vVS.jobLogInfo("Completed treewalk after populating /%v/", lostAndFoundDirName) 969 970 // TODO: Scan B+Tree placing orphaned non-DirInodes as elements of vVS.lostAndFoundDirInodeNumber 971 972 // Update incorrect LinkCounts 973 974 vVS.jobStartParallelism(validateVolumeFixLinkCountParallelism) 975 976 for inodeIndex = uint64(0); inodeIndex < uint64(inodeCount); inodeIndex++ { 977 if vVS.stopFlag { 978 vVS.childrenWaitGroup.Wait() 979 vVS.jobEndParallelism() 980 return 981 } 982 983 key, value, ok, err = vVS.inodeBPTree.GetByIndex(int(inodeIndex)) 984 if nil != err { 985 vVS.childrenWaitGroup.Wait() 986 vVS.jobEndParallelism() 987 vVS.jobLogErr("Got vVS.inodeBPTree.GetByIndex(0x%016X) failure: %v", inodeIndex, err) 988 return 989 } 990 if !ok { 991 vVS.childrenWaitGroup.Wait() 992 vVS.jobEndParallelism() 993 vVS.jobLogErr("Got vVS.inodeBPTree.GetByIndex(0x%016X) !ok", inodeIndex) 994 return 995 } 996 997 inodeNumber = key.(uint64) 998 linkCountComputed = value.(uint64) 999 1000 vVS.jobGrabParallelism() 1001 vVS.childrenWaitGroup.Add(1) 1002 go vVS.validateVolumeFixLinkCount(inodeNumber, linkCountComputed) 1003 } 1004 1005 vVS.childrenWaitGroup.Wait() 1006 1007 vVS.jobEndParallelism() 1008 1009 if vVS.stopFlag || (0 < len(vVS.err)) { 1010 return 1011 } 1012 1013 vVS.jobLogInfo("Completed LinkCount fix-up of all Inode's") 1014 1015 // If vVS.lostAndFoundDirInodeNumber is empty, remove it 1016 1017 _, moreEntries, err = vVS.inodeVolumeHandle.ReadDir(vVS.lostAndFoundDirInodeNumber, 2, 0) 1018 if nil != err { 1019 vVS.jobLogErr("Got ReadDir(vVS.lostAndFoundDirInodeNumber, 2, 0) failure: %v", err) 1020 return 1021 } 1022 1023 if moreEntries { 1024 vVS.jobLogInfo("Preserving non-empty /%v/", lostAndFoundDirName) 1025 } else { 1026 toDestroyInodeNumber, err = vVS.inodeVolumeHandle.Unlink(inode.RootDirInodeNumber, lostAndFoundDirName, false) 1027 if nil != err { 1028 vVS.jobLogErr("Got inode.Unlink(inode.RootDirInodeNumber, lostAndFoundDirName, false) failure: %v", err) 1029 return 1030 } 1031 1032 if inode.InodeNumber(0) != toDestroyInodeNumber { 1033 err = vVS.inodeVolumeHandle.Destroy(toDestroyInodeNumber) 1034 if nil != err { 1035 vVS.jobLogErr("Got inode.Destroy(toDestroyInodeNumber) failure: %v", err) 1036 return 1037 } 1038 } 1039 1040 vVS.jobLogInfo("Removed empty /%v/", lostAndFoundDirName) 1041 } 1042 1043 // Clean out unreferenced headhunter B+Tree "Objects") 1044 1045 vVS.objectBPTree = sortedmap.NewBPlusTree(jobBPTreeMaxKeysPerNode, sortedmap.CompareUint64, vVS, vVS.bpTreeCache) 1046 defer func(vVS *validateVolumeStruct) { 1047 var err error 1048 1049 err = vVS.objectBPTree.Discard() 1050 if nil != err { 1051 vVS.jobLogErr("Got vVS.objectBPTree.Discard() failure: %v", err) 1052 } 1053 }(vVS) 1054 1055 vVS.jobStartParallelism(validateVolumeIncrementByteCountsParallelism) 1056 1057 inodeIndex = 0 1058 1059 for { 1060 if vVS.stopFlag { 1061 vVS.childrenWaitGroup.Wait() 1062 vVS.jobEndParallelism() 1063 return 1064 } 1065 1066 inodeNumber, ok, err = vVS.headhunterVolumeHandle.IndexedInodeNumber(inodeIndex) 1067 if nil != err { 1068 vVS.jobLogErr("Got headhunter.IndexedInodeNumber(0x%016X) failure: %v", inodeIndex, err) 1069 vVS.childrenWaitGroup.Wait() 1070 vVS.jobEndParallelism() 1071 return 1072 } 1073 if !ok { 1074 break 1075 } 1076 1077 vVS.jobGrabParallelism() 1078 vVS.childrenWaitGroup.Add(1) 1079 go vVS.validateVolumeIncrementByteCounts(inodeNumber) 1080 1081 inodeIndex++ 1082 } 1083 1084 vVS.childrenWaitGroup.Wait() 1085 1086 vVS.jobEndParallelism() 1087 1088 if vVS.stopFlag || (0 < len(vVS.err)) { 1089 return 1090 } 1091 1092 objectIndex = 0 1093 1094 for { 1095 if vVS.stopFlag { 1096 return 1097 } 1098 1099 objectNumber, ok, err = vVS.headhunterVolumeHandle.IndexedBPlusTreeObjectNumber(objectIndex) 1100 if nil != err { 1101 vVS.jobLogErr("Got headhunter.IndexedBPlusTreeObjectNumber(0x%016X) failure: %v", objectIndex, err) 1102 return 1103 } 1104 1105 if !ok { 1106 break 1107 } 1108 1109 _, ok, err = vVS.objectBPTree.GetByKey(objectNumber) 1110 if nil != err { 1111 vVS.jobLogErr("Got vVS.objectBPTree.GetByKey(0x%016X) failure: %v", objectNumber, err) 1112 return 1113 } 1114 1115 if ok { 1116 objectIndex++ 1117 } else { 1118 err = vVS.headhunterVolumeHandle.DeleteBPlusTreeObject(objectNumber) 1119 if nil == err { 1120 vVS.jobLogInfo("Removed unreferenced headhunter B+Tree \"Object\" 0x%016X", objectNumber) 1121 } else { 1122 vVS.jobLogErr("Got headhunter.DeleteBPlusTreeObject(0x%016X) failure: %v", objectNumber, err) 1123 return 1124 } 1125 } 1126 } 1127 1128 if vVS.stopFlag || (0 < len(vVS.err)) { 1129 return 1130 } 1131 1132 vVS.jobLogInfo("Completed clean out unreferenced headhunter B+Tree \"Objects\"") 1133 1134 // TODO: Walk all FileInodes tracking referenced LogSegments 1135 // TODO: Remove non-Checkpoint Objects not in headhunter's LogSegment B+Tree 1136 // TODO: Delete unreferenced LogSegments (both headhunter records & Objects) 1137 1138 // Do a final checkpoint 1139 1140 err = vVS.headhunterVolumeHandle.DoCheckpoint() 1141 if nil != err { 1142 vVS.jobLogErr("Got headhunter.DoCheckpoint failure: %v", err) 1143 return 1144 } 1145 1146 vVS.jobLogInfo("Completed checkpoint after FSCK work") 1147 1148 // Validate headhunter checkpoint container contents 1149 1150 headhunterLayoutReport, discrepencies, err = vVS.headhunterVolumeHandle.FetchLayoutReport(headhunter.MergedBPlusTree, true) 1151 if nil != err { 1152 vVS.jobLogErr("Got headhunter.FetchLayoutReport() failure: %v", err) 1153 return 1154 } 1155 if 0 != discrepencies { 1156 vVS.jobLogErr("Found %v headhunter.FetchLayoutReport() discrepencies", discrepencies) 1157 return 1158 } 1159 1160 vVS.accountName, vVS.checkpointContainerName = vVS.headhunterVolumeHandle.FetchAccountAndCheckpointContainerNames() 1161 1162 _, checkpointContainerObjectList, err = swiftclient.ContainerGet(vVS.accountName, vVS.checkpointContainerName) 1163 if nil != err { 1164 vVS.jobLogErr("Got swiftclient.ContainerGet(\"%v\",\"%v\") failure: %v", vVS.accountName, vVS.checkpointContainerName, err) 1165 return 1166 } 1167 1168 for _, checkpointContainerObjectName = range checkpointContainerObjectList { 1169 checkpointContainerObjectNumber = uint64(0) // If remains 0 or results in returning to 0, 1170 // checkpointContainerObjectName should be deleted 1171 1172 if 16 == len(checkpointContainerObjectName) { 1173 if validObjectNameRE.MatchString(checkpointContainerObjectName) { 1174 checkpointContainerObjectNumber, err = strconv.ParseUint(checkpointContainerObjectName, 16, 64) 1175 if nil != err { 1176 vVS.jobLogErr("Got strconv.ParseUint(\"%v\",16,64) failure: %v", checkpointContainerObjectName, err) 1177 return // Note: Already scheduled async Object DELETEs will continue in the background 1178 } 1179 1180 _, ok = headhunterLayoutReport[checkpointContainerObjectNumber] 1181 if !ok { 1182 checkpointContainerObjectNumber = uint64(0) 1183 } 1184 } 1185 } 1186 1187 if uint64(0) == checkpointContainerObjectNumber { 1188 vVS.jobLogInfo("Removing unreferenced checkpointContainerObject %v", checkpointContainerObjectName) 1189 swiftclient.ObjectDelete(vVS.accountName, vVS.checkpointContainerName, checkpointContainerObjectName, swiftclient.SkipRetry) 1190 } 1191 1192 if vVS.stopFlag { 1193 return // Note: Already scheduled async Object DELETEs will continue in the background 1194 } 1195 } 1196 } 1197 1198 func (sVS *scrubVolumeStruct) Active() (active bool) { 1199 active = sVS.active 1200 return 1201 } 1202 1203 func (sVS *scrubVolumeStruct) Wait() { 1204 sVS.globalWaitGroup.Wait() 1205 } 1206 1207 func (sVS *scrubVolumeStruct) Cancel() { 1208 sVS.stopFlag = true 1209 sVS.Wait() 1210 } 1211 1212 func (sVS *scrubVolumeStruct) Error() (err []string) { 1213 var errString string 1214 1215 sVS.Lock() 1216 1217 err = make([]string, 0, len(sVS.err)) 1218 for _, errString = range sVS.err { 1219 err = append(err, errString) 1220 } 1221 1222 sVS.Unlock() 1223 1224 return 1225 } 1226 1227 func (sVS *scrubVolumeStruct) Info() (info []string) { 1228 var infoString string 1229 1230 sVS.Lock() 1231 1232 info = make([]string, 0, len(sVS.info)) 1233 for _, infoString = range sVS.info { 1234 info = append(info, infoString) 1235 } 1236 1237 sVS.Unlock() 1238 1239 return 1240 } 1241 1242 func (sVS *scrubVolumeStruct) scrubVolumeInode(inodeNumber uint64) { 1243 var ( 1244 err error 1245 inodeLock *dlm.RWLockStruct 1246 ) 1247 1248 defer sVS.childrenWaitGroup.Done() 1249 1250 defer sVS.jobReleaseParallelism() 1251 1252 inodeLock, err = sVS.jobStruct.inodeVolumeHandle.InitInodeLock(inode.InodeNumber(inodeNumber), nil) 1253 if nil != err { 1254 sVS.jobLogErr("Got initInodeLock(0x%016X) failure: %v", inodeNumber, err) 1255 return 1256 } 1257 err = inodeLock.WriteLock() 1258 if nil != err { 1259 sVS.jobLogErr("Got inodeLock.WriteLock() for Inode# 0x%016X failure: %v", inodeNumber, err) 1260 return 1261 } 1262 defer inodeLock.Unlock() 1263 1264 err = sVS.inodeVolumeHandle.Validate(inode.InodeNumber(inodeNumber), true) 1265 if nil != err { 1266 sVS.jobLogInfo("Got inode.Validate(0x%016X) failure: %v ... removing it", inodeNumber, err) 1267 1268 err = sVS.headhunterVolumeHandle.DeleteInodeRec(inodeNumber) 1269 if nil != err { 1270 sVS.jobLogErr("Got headhunter.DeleteInodeRec(0x%016X) failure: %v", inodeNumber, err) 1271 } 1272 1273 // Note: We could identify removal of an inode as an error here, but we don't 1274 // know if it is actually referenced. A subsequent validateVolume() call 1275 // would catch that condition. 1276 } 1277 } 1278 1279 func (sVS *scrubVolumeStruct) scrubVolume() { 1280 var ( 1281 err error 1282 inodeCount int 1283 inodeIndex uint64 1284 inodeNumber uint64 1285 key sortedmap.Key 1286 ok bool 1287 ) 1288 1289 sVS.jobLogInfo("SCRUB job initiated") 1290 1291 defer func(sVS *scrubVolumeStruct) { 1292 if sVS.stopFlag { 1293 sVS.jobLogInfo("SCRUB job stopped") 1294 } else if 0 == len(sVS.err) { 1295 sVS.jobLogInfo("SCRUB job completed without error") 1296 } else if 1 == len(sVS.err) { 1297 sVS.jobLogInfo("SCRUB job exited with one error") 1298 } else { 1299 sVS.jobLogInfo("SCRUB job exited with errors") 1300 } 1301 }(sVS) 1302 1303 defer func(sVS *scrubVolumeStruct) { 1304 sVS.active = false 1305 }(sVS) 1306 1307 defer sVS.globalWaitGroup.Done() 1308 1309 // Find specified volume 1310 1311 globals.Lock() 1312 1313 sVS.volume, ok = globals.volumeMap[sVS.volumeName] 1314 if !ok { 1315 globals.Unlock() 1316 sVS.jobLogErr("Couldn't find fs.volumeStruct") 1317 return 1318 } 1319 1320 globals.Unlock() 1321 1322 sVS.inodeVolumeHandle, err = inode.FetchVolumeHandle(sVS.volumeName) 1323 if nil != err { 1324 sVS.jobLogErr("Couldn't find inode.VolumeHandle") 1325 return 1326 } 1327 1328 sVS.headhunterVolumeHandle, err = headhunter.FetchVolumeHandle(sVS.volumeName) 1329 if nil != err { 1330 sVS.jobLogErr("Couldn't find headhunter.VolumeHandle") 1331 return 1332 } 1333 1334 // Setup B+Tree to hold arbitrarily sized map[uint64]uint64 (i.e. beyond what will fit in memoory) 1335 1336 sVS.bpTreeFile, err = ioutil.TempFile("", "ProxyFS_ScrubVolume_") 1337 if nil != err { 1338 sVS.jobLogErr("Got ioutil.TempFile() failure: %v", err) 1339 return 1340 } 1341 defer func(sVS *scrubVolumeStruct) { 1342 var ( 1343 bpTreeFileName string 1344 ) 1345 1346 bpTreeFileName = sVS.bpTreeFile.Name() 1347 _ = sVS.bpTreeFile.Close() 1348 _ = os.Remove(bpTreeFileName) 1349 }(sVS) 1350 1351 sVS.bpTreeFileNextOffset = 0 1352 1353 sVS.bpTreeCache = sortedmap.NewBPlusTreeCache(jobBPTreeEvictLowLimit, jobBPTreeEvictHighLimit) 1354 1355 sVS.inodeBPTree = sortedmap.NewBPlusTree(jobBPTreeMaxKeysPerNode, sortedmap.CompareUint64, sVS, sVS.bpTreeCache) 1356 defer func(sVS *scrubVolumeStruct) { 1357 var err error 1358 1359 err = sVS.inodeBPTree.Discard() 1360 if nil != err { 1361 sVS.jobLogErr("Got sVS.inodeBPTree.Discard() failure: %v", err) 1362 } 1363 }(sVS) 1364 1365 sVS.jobLogInfo("Beginning enumeration of inodes to be deeply validated") 1366 1367 sVS.volume.jobRWMutex.Lock() 1368 1369 inodeIndex = 0 1370 1371 for { 1372 if sVS.stopFlag { 1373 sVS.volume.jobRWMutex.Unlock() 1374 return 1375 } 1376 1377 inodeNumber, ok, err = sVS.headhunterVolumeHandle.IndexedInodeNumber(inodeIndex) 1378 if nil != err { 1379 sVS.volume.jobRWMutex.Unlock() 1380 sVS.jobLogErrWhileLocked("Got headhunter.IndexedInodeNumber(0x%016X) failure: %v", inodeIndex, err) 1381 return 1382 } 1383 if !ok { 1384 break 1385 } 1386 1387 ok, err = sVS.inodeBPTree.Put(inodeNumber, uint64(0)) // Unused LinkCount - just set it to 0 1388 if nil != err { 1389 sVS.volume.jobRWMutex.Unlock() 1390 sVS.jobLogErrWhileLocked("Got sVS.inodeBPTree.Put(0x%016X, 0) failure: %v", inodeNumber, err) 1391 return 1392 } 1393 if !ok { 1394 sVS.volume.jobRWMutex.Unlock() 1395 sVS.jobLogErrWhileLocked("Got sVS.inodeBPTree.Put(0x%016X, 0) !ok", inodeNumber) 1396 return 1397 } 1398 1399 inodeIndex++ 1400 } 1401 1402 sVS.volume.jobRWMutex.Unlock() 1403 1404 sVS.jobLogInfo("Completed enumeration of inodes to be deeply validated") 1405 1406 sVS.jobLogInfo("Beginning deep validation of inodes") 1407 1408 inodeCount, err = sVS.inodeBPTree.Len() 1409 if nil != err { 1410 sVS.jobLogErr("Got sVS.inodeBPTree.Len() failure: %v", err) 1411 return 1412 } 1413 1414 sVS.jobStartParallelism(scrubVolumeInodeParallelism) 1415 1416 for inodeIndex = uint64(0); inodeIndex < uint64(inodeCount); inodeIndex++ { 1417 if sVS.stopFlag { 1418 sVS.childrenWaitGroup.Wait() 1419 sVS.jobEndParallelism() 1420 return 1421 } 1422 1423 key, _, ok, err = sVS.inodeBPTree.GetByIndex(int(inodeIndex)) 1424 if nil != err { 1425 sVS.jobLogErr("Got sVS.inodeBPTree.GetByIndex(0x%016X) failure: %v", inodeIndex, err) 1426 return 1427 } 1428 if !ok { 1429 sVS.jobLogErr("Got sVS.inodeBPTree.GetByIndex(0x%016X) !ok", inodeIndex) 1430 return 1431 } 1432 1433 inodeNumber = key.(uint64) 1434 1435 sVS.jobGrabParallelism() 1436 sVS.childrenWaitGroup.Add(1) 1437 go sVS.scrubVolumeInode(inodeNumber) 1438 } 1439 1440 sVS.childrenWaitGroup.Wait() 1441 1442 sVS.jobEndParallelism() 1443 1444 sVS.jobLogInfo("Completed deep validation of inodes") 1445 }