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  }