github.com/rohankumardubey/proxyfs@v0.0.0-20210108201508-653efa9ab00e/inode/file.go (about)

     1  package inode
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"time"
     7  
     8  	"github.com/swiftstack/sortedmap"
     9  
    10  	"github.com/swiftstack/ProxyFS/blunder"
    11  	"github.com/swiftstack/ProxyFS/headhunter"
    12  	"github.com/swiftstack/ProxyFS/logger"
    13  	"github.com/swiftstack/ProxyFS/stats"
    14  	"github.com/swiftstack/ProxyFS/utils"
    15  )
    16  
    17  type fileExtentStruct struct {
    18  	FileOffset       uint64
    19  	Length           uint64
    20  	LogSegmentNumber uint64
    21  	LogSegmentOffset uint64
    22  }
    23  
    24  func (vS *volumeStruct) CreateFile(filePerm InodeMode, userID InodeUserID, groupID InodeGroupID) (fileInodeNumber InodeNumber, err error) {
    25  	err = enforceRWMode(false)
    26  	if nil != err {
    27  		return
    28  	}
    29  
    30  	fileInode, err := vS.createFileInode(filePerm, userID, groupID)
    31  	if err != nil {
    32  		return 0, err
    33  	}
    34  	return fileInode.InodeNumber, nil
    35  }
    36  
    37  // REVIEW TODO: Should one of these (and its siblings, CreateDir & CreateSymlink) be doing flush here?
    38  
    39  func (vS *volumeStruct) createFileInode(filePerm InodeMode, userID InodeUserID, groupID InodeGroupID) (fileInode *inMemoryInodeStruct, err error) {
    40  	stats.IncrementOperations(&stats.FileCreateOps)
    41  
    42  	// Create file mode out of file permissions plus inode type
    43  	fileMode, err := determineMode(filePerm, FileType)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	fileInode, err = vS.makeInMemoryInode(FileType, fileMode, userID, groupID)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	fileInode.dirty = true
    54  
    55  	// The payload of a file inode is a B+-tree map whose keys are uint64 file
    56  	// offsets and whose values are `fileExtent`s.
    57  
    58  	extents :=
    59  		sortedmap.NewBPlusTree(
    60  			vS.maxExtentsPerFileNode,
    61  			sortedmap.CompareUint64,
    62  			&fileInodeCallbacks{treeNodeLoadable{inode: fileInode}},
    63  			globals.fileExtentMapCache)
    64  
    65  	fileInode.payload = extents
    66  
    67  	ok, err := vS.inodeCacheInsert(fileInode)
    68  	if nil != err {
    69  		return
    70  	}
    71  	if !ok {
    72  		err = fmt.Errorf("inodeCacheInsert(fileInode) failed")
    73  		return
    74  	}
    75  
    76  	stats.IncrementOperations(&stats.FileCreateSuccessOps)
    77  	return
    78  }
    79  
    80  func fileLen(extents sortedmap.BPlusTree) uint64 {
    81  	numExtents, err := extents.Len()
    82  	if nil != err {
    83  		panic(err)
    84  	}
    85  	if numExtents == 0 {
    86  		return 0
    87  	}
    88  
    89  	_, value, ok, err := extents.GetByIndex(numExtents - 1)
    90  	if nil != err {
    91  		panic(err)
    92  	}
    93  	if !ok {
    94  		panic("couldn't find last extent of nonempty file")
    95  	}
    96  
    97  	lastFileExtent := value.(*fileExtentStruct)
    98  
    99  	return lastFileExtent.FileOffset + lastFileExtent.Length
   100  }
   101  
   102  // Update a file inode to have a new size, zero-padding as necessary.
   103  //
   104  // Doesn't flush anything.
   105  func setSizeInMemory(fileInode *inMemoryInodeStruct, size uint64) (err error) {
   106  	extents := fileInode.payload.(sortedmap.BPlusTree)
   107  	extentIndex, found, err := extents.BisectLeft(size)
   108  	if nil != err {
   109  		panic(err)
   110  	}
   111  
   112  	if !found {
   113  		if 0 <= extentIndex {
   114  			// Potentially trim preceeding extent
   115  			_, extentValue, ok, getByIndexErr := extents.GetByIndex(extentIndex)
   116  			if nil != getByIndexErr {
   117  				panic(getByIndexErr)
   118  			}
   119  			if !ok {
   120  				unexpectedErr := fmt.Errorf("unexpected extents indexing problem")
   121  				panic(unexpectedErr)
   122  			}
   123  			extent := extentValue.(*fileExtentStruct)
   124  			if (extent.FileOffset + extent.Length) > size {
   125  				// Yes, we need to trim the preceeding extent
   126  				trimSize := extent.Length - (size - extent.FileOffset)
   127  				extent.Length -= trimSize
   128  				ok, patchByIndexErr := extents.PatchByIndex(extentIndex, extent)
   129  				if nil != patchByIndexErr {
   130  					panic(patchByIndexErr)
   131  				}
   132  				if !ok {
   133  					unexpectedErr := fmt.Errorf("unexpected extents indexing problem")
   134  					panic(unexpectedErr)
   135  				}
   136  				decrementLogSegmentMapFileData(fileInode, extent.LogSegmentNumber, trimSize)
   137  			}
   138  		}
   139  
   140  		extentIndex++ // Step to next extent entry (beyond potentially truncated preceeding extent)
   141  	}
   142  
   143  	for {
   144  		_, extentValue, ok, getByIndexErr := extents.GetByIndex(extentIndex)
   145  		if nil != getByIndexErr {
   146  			panic(getByIndexErr)
   147  		}
   148  		if !ok {
   149  			break
   150  		}
   151  		extent := extentValue.(*fileExtentStruct)
   152  		decrementLogSegmentMapFileData(fileInode, extent.LogSegmentNumber, extent.Length)
   153  		ok, deleteByIndexErr := extents.DeleteByIndex(extentIndex)
   154  		if nil != deleteByIndexErr {
   155  			panic(deleteByIndexErr)
   156  		}
   157  		if !ok {
   158  			deleteByIndexErr = fmt.Errorf("extent just indexed should have been delete-able")
   159  			panic(deleteByIndexErr)
   160  		}
   161  	}
   162  
   163  	fileInode.dirty = true
   164  	fileInode.Size = size
   165  
   166  	updateTime := time.Now()
   167  	fileInode.ModificationTime = updateTime
   168  	fileInode.AttrChangeTime = updateTime
   169  	return
   170  }
   171  
   172  func (vS *volumeStruct) Read(fileInodeNumber InodeNumber, offset uint64, length uint64, profiler *utils.Profiler) (buf []byte, err error) {
   173  	var (
   174  		fileInode     *inMemoryInodeStruct
   175  		readPlan      []ReadPlanStep
   176  		readPlanBytes uint64
   177  		snapShotID    uint64
   178  	)
   179  
   180  	fileInode, err = vS.fetchInodeType(fileInodeNumber, FileType)
   181  	if nil != err {
   182  		logger.ErrorWithError(err)
   183  		return
   184  	}
   185  
   186  	_, snapShotID, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber))
   187  
   188  	readPlan, readPlanBytes, err = vS.getReadPlanHelper(snapShotID, fileInode, &offset, &length)
   189  	if nil != err {
   190  		logger.WarnWithError(err)
   191  		return
   192  	}
   193  
   194  	buf, err = vS.doReadPlan(fileInode, readPlan, readPlanBytes)
   195  	if nil != err {
   196  		logger.WarnWithError(err)
   197  		return
   198  	}
   199  
   200  	stats.IncrementOperationsAndBucketedBytes(stats.FileRead, uint64(len(buf)))
   201  
   202  	err = nil
   203  	return
   204  }
   205  
   206  func (vS *volumeStruct) GetReadPlan(fileInodeNumber InodeNumber, offset *uint64, length *uint64) (readPlan []ReadPlanStep, err error) {
   207  	var (
   208  		readPlanBytes uint64
   209  		snapShotID    uint64
   210  	)
   211  
   212  	fileInode, err := vS.fetchInodeType(fileInodeNumber, FileType)
   213  	if nil != err {
   214  		logger.ErrorWithError(err)
   215  		return
   216  	}
   217  
   218  	if fileInode.dirty {
   219  		err = flush(fileInode, false)
   220  		if nil != err {
   221  			logger.ErrorWithError(err)
   222  			return
   223  		}
   224  	}
   225  
   226  	_, snapShotID, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber))
   227  
   228  	readPlan, readPlanBytes, err = vS.getReadPlanHelper(snapShotID, fileInode, offset, length)
   229  	if nil != err {
   230  		logger.ErrorWithError(err)
   231  		return
   232  	}
   233  
   234  	stats.IncrementOperationsBucketedEntriesAndBucketedBytes(stats.FileReadplan, uint64(len(readPlan)), readPlanBytes)
   235  	return
   236  }
   237  
   238  func (vS *volumeStruct) getReadPlanHelper(snapShotID uint64, fileInode *inMemoryInodeStruct, requestedOffset *uint64, requestedLength *uint64) (readPlan []ReadPlanStep, readPlanBytes uint64, err error) {
   239  	var (
   240  		offset uint64
   241  	)
   242  
   243  	readPlan = make([]ReadPlanStep, 0)
   244  
   245  	if requestedOffset == nil && requestedLength == nil {
   246  		err = fmt.Errorf("requestedOffset and requestedLength cannot both be nil")
   247  		return
   248  	} else if requestedOffset == nil {
   249  		// Suffix request, e.g. "bytes=-10", the last 10 bytes of the file
   250  		if fileInode.Size > *requestedLength {
   251  			offset = fileInode.Size - *requestedLength
   252  			readPlanBytes = *requestedLength
   253  		} else {
   254  			// A suffix request for more bytes than the file has must get the whole file.
   255  			offset = 0
   256  			readPlanBytes = fileInode.Size
   257  		}
   258  	} else if requestedLength == nil {
   259  		// Prefix request, e.g. "bytes=25-", from byte 25 to the end
   260  		offset = *requestedOffset
   261  		readPlanBytes = fileInode.Size - *requestedOffset
   262  	} else {
   263  		offset = *requestedOffset
   264  		readPlanBytes = *requestedLength
   265  	}
   266  
   267  	if (offset + readPlanBytes) > fileInode.Size {
   268  		if fileInode.Size > offset {
   269  			readPlanBytes = fileInode.Size - offset
   270  		} else {
   271  			readPlanBytes = 0
   272  		}
   273  	}
   274  
   275  	if 0 == readPlanBytes {
   276  		return
   277  	}
   278  
   279  	extents := fileInode.payload.(sortedmap.BPlusTree)
   280  
   281  	curOffset := offset
   282  	terminalOffset := offset + readPlanBytes
   283  
   284  	curExtentIndex, _, err := extents.BisectLeft(offset)
   285  	if nil != err {
   286  		panic(err)
   287  	}
   288  
   289  	for {
   290  		if 0 > curExtentIndex {
   291  			// Skip to next (0th) extent
   292  			curExtentIndex++
   293  		}
   294  
   295  		// Fetch curExtent
   296  		_, curExtentValue, ok, getByIndexErr := extents.GetByIndex(curExtentIndex)
   297  		if nil != getByIndexErr {
   298  			panic(getByIndexErr)
   299  		}
   300  		if !ok {
   301  			// We have reached the end of extents
   302  			break
   303  		}
   304  		curExtent := curExtentValue.(*fileExtentStruct)
   305  
   306  		if curExtent.FileOffset >= terminalOffset {
   307  			// [curOffset:terminalOffset) ends before curExtent... so we need look no further
   308  			break
   309  		}
   310  
   311  		if (curExtent.FileOffset + curExtent.Length) <= curOffset {
   312  			// [curOffset:terminalOffset) starts beyond curExtent... so move to the next extent
   313  			curExtentIndex++
   314  			continue
   315  		}
   316  
   317  		// At this point, we know [curOffset:terminalOffset) intersects curExtent
   318  
   319  		if curOffset < curExtent.FileOffset {
   320  			// [curOffset:terminalOffset) starts before curExtent... so insert a zero-fill ReadPlanStep to get to curExtent
   321  			step := ReadPlanStep{
   322  				LogSegmentNumber: 0,
   323  				Offset:           0,
   324  				Length:           curExtent.FileOffset - curOffset,
   325  				AccountName:      "",
   326  				ContainerName:    "",
   327  				ObjectName:       "",
   328  				ObjectPath:       "",
   329  			}
   330  			readPlan = append(readPlan, step)
   331  			curOffset = curExtent.FileOffset
   332  		}
   333  
   334  		skipSize := uint64(0)
   335  
   336  		if curOffset > curExtent.FileOffset {
   337  			// [curOffset:terminalOffset) starts after curExtent... so update skipSize appropriately
   338  			skipSize = curOffset - curExtent.FileOffset
   339  		}
   340  
   341  		if terminalOffset <= (curExtent.FileOffset + curExtent.Length) {
   342  			// [curOffset:terminalOffset) is completed by some or all of curExtent
   343  			step := ReadPlanStep{
   344  				LogSegmentNumber: vS.headhunterVolumeHandle.SnapShotIDAndNonceEncode(snapShotID, curExtent.LogSegmentNumber),
   345  				Offset:           curExtent.LogSegmentOffset + skipSize,
   346  				Length:           terminalOffset - curOffset,
   347  				AccountName:      vS.accountName,
   348  			}
   349  			step.ContainerName, step.ObjectName, step.ObjectPath, err = vS.getObjectLocationFromLogSegmentNumber(step.LogSegmentNumber)
   350  			if nil != err {
   351  				return
   352  			}
   353  			readPlan = append(readPlan, step)
   354  			curOffset = terminalOffset
   355  			break
   356  		} else {
   357  			// [curOffset:terminalOffset) extends beyond curExtent
   358  			step := ReadPlanStep{
   359  				LogSegmentNumber: vS.headhunterVolumeHandle.SnapShotIDAndNonceEncode(snapShotID, curExtent.LogSegmentNumber),
   360  				Offset:           curExtent.LogSegmentOffset + skipSize,
   361  				Length:           curExtent.Length - skipSize,
   362  				AccountName:      vS.accountName,
   363  			}
   364  			step.ContainerName, step.ObjectName, step.ObjectPath, err = vS.getObjectLocationFromLogSegmentNumber(step.LogSegmentNumber)
   365  			if nil != err {
   366  				return
   367  			}
   368  			readPlan = append(readPlan, step)
   369  			curOffset += step.Length
   370  			curExtentIndex++
   371  		}
   372  	}
   373  
   374  	// Append trailing zero-fill ReadPlanStep if necessary
   375  
   376  	if curOffset < terminalOffset {
   377  		// We need a trailing zero-fill ReadPlanStep
   378  		step := ReadPlanStep{
   379  			LogSegmentNumber: 0,
   380  			Offset:           0,
   381  			Length:           terminalOffset - curOffset,
   382  			AccountName:      "",
   383  			ContainerName:    "",
   384  			ObjectName:       "",
   385  			ObjectPath:       "",
   386  		}
   387  
   388  		readPlan = append(readPlan, step)
   389  	}
   390  
   391  	err = nil
   392  	return
   393  }
   394  
   395  func (vS *volumeStruct) FetchExtentMapChunk(fileInodeNumber InodeNumber, fileOffset uint64, maxEntriesFromFileOffset int64, maxEntriesBeforeFileOffset int64) (extentMapChunk *ExtentMapChunkStruct, err error) {
   396  	var (
   397  		containerName               string
   398  		encodedLogSegmentNumber     uint64
   399  		extentMap                   sortedmap.BPlusTree
   400  		extentMapLen                int
   401  		extentMapIndex              int
   402  		extentMapIndexAtOffset      int
   403  		extentMapIndexAtOffsetFound bool
   404  		extentMapIndexEnd           int
   405  		extentMapIndexStart         int
   406  		fileExtent                  *fileExtentStruct
   407  		fileExtentAsValue           sortedmap.Value
   408  		fileInode                   *inMemoryInodeStruct
   409  		objectName                  string
   410  		snapShotID                  uint64
   411  	)
   412  
   413  	// Validate args
   414  
   415  	if maxEntriesFromFileOffset < 1 {
   416  		err = fmt.Errorf("inode.FetchExtentMap() requires maxEntriesFromOffset (%d) >= 1", maxEntriesFromFileOffset)
   417  		return
   418  	}
   419  	if maxEntriesBeforeFileOffset < 0 {
   420  		err = fmt.Errorf("inode.FetchExtentMap() requires maxEntriesBeforeOffset (%d) >= 0", maxEntriesBeforeFileOffset)
   421  		return
   422  	}
   423  
   424  	fileInode, err = vS.fetchInodeType(fileInodeNumber, FileType)
   425  	if nil != err {
   426  		return
   427  	}
   428  
   429  	// Ensure in-flight LogSegments are flushed
   430  
   431  	if fileInode.dirty {
   432  		err = flush(fileInode, false)
   433  		if nil != err {
   434  			logger.ErrorWithError(err)
   435  			return
   436  		}
   437  	}
   438  
   439  	// Locate extent that either contains fileOffset,
   440  	//   or if no extent does, that we select the one just after fileOffset
   441  
   442  	extentMap = fileInode.payload.(sortedmap.BPlusTree)
   443  
   444  	extentMapLen, err = extentMap.Len()
   445  	if nil != err {
   446  		panic(err)
   447  	}
   448  
   449  	if 0 == extentMapLen {
   450  		// In the absence of any extents, just describe entire fileInode as zero-filled
   451  
   452  		extentMapChunk = &ExtentMapChunkStruct{
   453  			FileOffsetRangeStart: 0,
   454  			FileOffsetRangeEnd:   fileInode.Size,
   455  			FileSize:             fileInode.Size,
   456  			ExtentMapEntry:       make([]ExtentMapEntryStruct, 0),
   457  		}
   458  		return
   459  	}
   460  
   461  	extentMapIndexAtOffset, extentMapIndexAtOffsetFound, err = extentMap.BisectLeft(fileOffset)
   462  	if nil != err {
   463  		panic(err)
   464  	}
   465  
   466  	if !extentMapIndexAtOffsetFound {
   467  		if extentMapIndexAtOffset >= 0 {
   468  			_, fileExtentAsValue, _, err = extentMap.GetByIndex(extentMapIndexAtOffset)
   469  			if nil != err {
   470  				panic(err)
   471  			}
   472  
   473  			fileExtent = fileExtentAsValue.(*fileExtentStruct)
   474  
   475  			if (fileExtent.FileOffset + fileExtent.Length) <= fileOffset {
   476  				extentMapIndexAtOffset++
   477  			}
   478  		} else {
   479  			extentMapIndexAtOffset = 0
   480  		}
   481  	}
   482  
   483  	// Compute extent indices surrounding fileOffset that are also requested
   484  
   485  	if int64(extentMapIndexAtOffset) > maxEntriesBeforeFileOffset {
   486  		extentMapIndexStart = extentMapIndexAtOffset - int(maxEntriesBeforeFileOffset)
   487  	} else {
   488  		extentMapIndexStart = 0
   489  	}
   490  
   491  	if int64(extentMapLen-extentMapIndexAtOffset) <= maxEntriesFromFileOffset {
   492  		extentMapIndexEnd = extentMapLen - 1
   493  	} else {
   494  		extentMapIndexEnd = extentMapIndexAtOffset + int(maxEntriesFromFileOffset) - 1
   495  	}
   496  
   497  	// Populate extentMapChunk with selected extents
   498  
   499  	_, snapShotID, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber))
   500  
   501  	extentMapChunk = &ExtentMapChunkStruct{
   502  		FileSize:       fileInode.Size,
   503  		ExtentMapEntry: make([]ExtentMapEntryStruct, 0, extentMapIndexEnd-extentMapIndexStart+1),
   504  	}
   505  
   506  	// Fill in FileOffsetRangeStart to include zero-filled (non-)extent just before first returned extent
   507  
   508  	if extentMapIndexStart > 0 {
   509  		_, fileExtentAsValue, _, err = extentMap.GetByIndex(extentMapIndexStart - 1)
   510  		if nil != err {
   511  			panic(err)
   512  		}
   513  
   514  		fileExtent = fileExtentAsValue.(*fileExtentStruct)
   515  
   516  		extentMapChunk.FileOffsetRangeStart = fileExtent.FileOffset + fileExtent.Length
   517  	} else {
   518  		extentMapChunk.FileOffsetRangeStart = 0
   519  	}
   520  
   521  	for extentMapIndex = extentMapIndexStart; extentMapIndex <= extentMapIndexEnd; extentMapIndex++ {
   522  		_, fileExtentAsValue, _, err = extentMap.GetByIndex(extentMapIndex)
   523  		if nil != err {
   524  			panic(err)
   525  		}
   526  
   527  		fileExtent = fileExtentAsValue.(*fileExtentStruct)
   528  
   529  		encodedLogSegmentNumber = vS.headhunterVolumeHandle.SnapShotIDAndNonceEncode(snapShotID, fileExtent.LogSegmentNumber)
   530  
   531  		containerName, objectName, _, err = vS.getObjectLocationFromLogSegmentNumber(encodedLogSegmentNumber)
   532  		if nil != err {
   533  			panic(err)
   534  		}
   535  
   536  		extentMapChunk.ExtentMapEntry = append(extentMapChunk.ExtentMapEntry, ExtentMapEntryStruct{
   537  			FileOffset:       fileExtent.FileOffset,
   538  			LogSegmentOffset: fileExtent.LogSegmentOffset,
   539  			Length:           fileExtent.Length,
   540  			ContainerName:    containerName,
   541  			ObjectName:       objectName,
   542  		})
   543  	}
   544  
   545  	// Fill in FileOffsetRangeEnd to included zero-filled (non-)extent just after last returned extent
   546  
   547  	if (extentMapIndexEnd + 1) == extentMapLen {
   548  		extentMapChunk.FileOffsetRangeEnd = fileInode.Size
   549  	} else {
   550  		_, fileExtentAsValue, _, err = extentMap.GetByIndex(extentMapIndexEnd + 1)
   551  		if nil != err {
   552  			panic(err)
   553  		}
   554  
   555  		fileExtent = fileExtentAsValue.(*fileExtentStruct)
   556  
   557  		extentMapChunk.FileOffsetRangeEnd = fileExtent.FileOffset
   558  	}
   559  
   560  	return
   561  }
   562  
   563  func incrementLogSegmentMapFileData(fileInode *inMemoryInodeStruct, logSegmentNumber uint64, incrementAmount uint64) {
   564  	logSegmentRecord, ok := fileInode.LogSegmentMap[logSegmentNumber]
   565  	if ok {
   566  		logSegmentRecord += incrementAmount
   567  		fileInode.LogSegmentMap[logSegmentNumber] = logSegmentRecord
   568  	} else {
   569  		fileInode.LogSegmentMap[logSegmentNumber] = incrementAmount
   570  	}
   571  }
   572  
   573  func decrementLogSegmentMapFileData(fileInode *inMemoryInodeStruct, logSegmentNumber uint64, decrementAmount uint64) {
   574  	logSegmentRecord, ok := fileInode.LogSegmentMap[logSegmentNumber]
   575  	if ok {
   576  		if decrementAmount > logSegmentRecord {
   577  			err := fmt.Errorf("Unexpected decrementLogSegmentMapFileData() call would make FileData \"go negative\"")
   578  			panic(err)
   579  		}
   580  		logSegmentRecord -= decrementAmount
   581  		fileInode.LogSegmentMap[logSegmentNumber] = logSegmentRecord
   582  	} else {
   583  		err := fmt.Errorf("Unexpected decrementLogSegmentMapFileData() call referenced non-existent logSegmentNumber")
   584  		panic(err)
   585  	}
   586  }
   587  
   588  // `recordWrite` is called by `Write` and `Wrote` to update the file inode
   589  // payload's record of the extents that compose the file.
   590  func recordWrite(fileInode *inMemoryInodeStruct, fileOffset uint64, length uint64, logSegmentNumber uint64, logSegmentOffset uint64) (err error) {
   591  	extents := fileInode.payload.(sortedmap.BPlusTree)
   592  
   593  	// First we need to eliminate extents or portions thereof that overlap the specified write
   594  
   595  	extentIndex, found, err := extents.BisectLeft(fileOffset)
   596  	if nil != err {
   597  		panic(err)
   598  	}
   599  
   600  	if !found {
   601  		if 0 <= extentIndex {
   602  			// Potentially split preceeding extent
   603  			_, extentValue, ok, getByIndexErr := extents.GetByIndex(extentIndex)
   604  			if nil != getByIndexErr {
   605  				panic(getByIndexErr)
   606  			}
   607  			if !ok {
   608  				unexpectedErr := fmt.Errorf("unexpected extents indexing problem")
   609  				panic(unexpectedErr)
   610  			}
   611  			leftExtent := extentValue.(*fileExtentStruct)
   612  			if (leftExtent.FileOffset + leftExtent.Length) > fileOffset {
   613  				// Yes, we need to split the preceeding extent
   614  				splitOutSize := (leftExtent.FileOffset + leftExtent.Length) - fileOffset
   615  				leftExtent.Length -= splitOutSize
   616  				ok, patchByIndexErr := extents.PatchByIndex(extentIndex, leftExtent)
   617  				if nil != patchByIndexErr {
   618  					panic(patchByIndexErr)
   619  				}
   620  				if !ok {
   621  					unexpectedErr := fmt.Errorf("unexpected extents indexing problem")
   622  					panic(unexpectedErr)
   623  				}
   624  				rightExtent := &fileExtentStruct{
   625  					FileOffset:       fileOffset,
   626  					Length:           splitOutSize,
   627  					LogSegmentNumber: leftExtent.LogSegmentNumber,
   628  					LogSegmentOffset: leftExtent.LogSegmentOffset + leftExtent.Length,
   629  				}
   630  				ok, putErr := extents.Put(rightExtent.FileOffset, rightExtent)
   631  				if nil != putErr {
   632  					panic(putErr)
   633  				}
   634  				if !ok {
   635  					unexpectedErr := fmt.Errorf("unexpected extents key problem")
   636  					panic(unexpectedErr)
   637  				}
   638  			}
   639  		}
   640  
   641  		extentIndex++ // Step to next extent entry (beyond potentially truncated preceeding extent)
   642  	}
   643  
   644  	for {
   645  		_, extentValue, ok, getByIndexErr := extents.GetByIndex(extentIndex)
   646  		if nil != getByIndexErr {
   647  			panic(getByIndexErr)
   648  		}
   649  		if !ok {
   650  			// We have reached the end of extents
   651  			break
   652  		}
   653  		leftExtent := extentValue.(*fileExtentStruct)
   654  		if leftExtent.FileOffset >= (fileOffset + length) {
   655  			// We are done pruning extents
   656  			break
   657  		}
   658  		if (fileOffset + length) >= (leftExtent.FileOffset + leftExtent.Length) {
   659  			// This extent entirely overwritten... just delete it
   660  			decrementLogSegmentMapFileData(fileInode, leftExtent.LogSegmentNumber, leftExtent.Length)
   661  			ok, deleteByIndexErr := extents.DeleteByIndex(extentIndex)
   662  			if nil != deleteByIndexErr {
   663  				panic(deleteByIndexErr)
   664  			}
   665  			if !ok {
   666  				unexpectedErr := fmt.Errorf("unexpected extents indexing problem")
   667  				panic(unexpectedErr)
   668  			}
   669  		} else {
   670  			// This extent partially overwritten... trim it from the front and we will be done trimming
   671  			ok, deleteByIndexErr := extents.DeleteByIndex(extentIndex)
   672  			if nil != deleteByIndexErr {
   673  				panic(deleteByIndexErr)
   674  			}
   675  			if !ok {
   676  				unexpectedErr := fmt.Errorf("unexpected extents indexing problem")
   677  				panic(unexpectedErr)
   678  			}
   679  			overlapSize := (fileOffset + length) - leftExtent.FileOffset
   680  			rightExtent := &fileExtentStruct{
   681  				FileOffset:       leftExtent.FileOffset + overlapSize,
   682  				Length:           leftExtent.Length - overlapSize,
   683  				LogSegmentNumber: leftExtent.LogSegmentNumber,
   684  				LogSegmentOffset: leftExtent.LogSegmentOffset + overlapSize,
   685  			}
   686  			ok, putErr := extents.Put(rightExtent.FileOffset, rightExtent)
   687  			if nil != putErr {
   688  				panic(putErr)
   689  			}
   690  			if !ok {
   691  				unexpectedErr := fmt.Errorf("unexpected extents key problem")
   692  				panic(unexpectedErr)
   693  			}
   694  			decrementLogSegmentMapFileData(fileInode, leftExtent.LogSegmentNumber, overlapSize)
   695  			break
   696  		}
   697  	}
   698  
   699  	// Now that there will be no overlap, see if we can append to the preceding fileExtent
   700  
   701  	prevIndex, found, err := extents.BisectLeft(fileOffset)
   702  	if nil != err {
   703  		panic(err)
   704  	}
   705  	if found {
   706  		unexpectedErr := fmt.Errorf("unexpected to find fileOffset in extents at this point)")
   707  		panic(unexpectedErr)
   708  	}
   709  
   710  	var prevExtent *fileExtentStruct
   711  
   712  	prevExtent = nil
   713  
   714  	if 0 <= prevIndex {
   715  		_, prevExtentValue, ok, getByIndexErr := extents.GetByIndex(prevIndex)
   716  		if nil != getByIndexErr {
   717  			panic(getByIndexErr)
   718  		}
   719  		if !ok {
   720  			unexpectedErr := fmt.Errorf("unexpected to not find predecessor in extents at this point")
   721  			panic(unexpectedErr)
   722  		}
   723  
   724  		prevExtent = prevExtentValue.(*fileExtentStruct)
   725  	}
   726  
   727  	if (nil != prevExtent) && (prevExtent.LogSegmentNumber == logSegmentNumber) && ((prevExtent.FileOffset + prevExtent.Length) == fileOffset) && ((prevExtent.LogSegmentOffset + prevExtent.Length) == logSegmentOffset) {
   728  		// APPEND Case: We are able to simply lengthen prevExtent
   729  
   730  		prevExtent.Length += length
   731  		ok, patchByIndexErr := extents.PatchByIndex(prevIndex, prevExtent)
   732  		if nil != patchByIndexErr {
   733  			panic(patchByIndexErr)
   734  		}
   735  		if !ok {
   736  			unexpectedErr := fmt.Errorf("unexpected to not be able to PATCH at this point")
   737  			panic(unexpectedErr)
   738  		}
   739  	} else {
   740  		// Non-APPEND Case: We need to insert a new extent
   741  
   742  		newExtent := &fileExtentStruct{
   743  			FileOffset:       fileOffset,
   744  			Length:           length,
   745  			LogSegmentNumber: logSegmentNumber,
   746  			LogSegmentOffset: logSegmentOffset,
   747  		}
   748  
   749  		ok, putErr := extents.Put(newExtent.FileOffset, newExtent)
   750  		if nil != putErr {
   751  			panic(putErr)
   752  		}
   753  		if !ok {
   754  			unexpectedErr := fmt.Errorf("unexpected to not be able to PUT at this point")
   755  			panic(unexpectedErr)
   756  		}
   757  	}
   758  
   759  	if (fileOffset + length) > fileInode.Size {
   760  		fileInode.Size = fileOffset + length
   761  	}
   762  
   763  	incrementLogSegmentMapFileData(fileInode, logSegmentNumber, length)
   764  
   765  	return nil
   766  }
   767  
   768  func (vS *volumeStruct) Write(fileInodeNumber InodeNumber, offset uint64, buf []byte, profiler *utils.Profiler) (err error) {
   769  	err = enforceRWMode(true)
   770  	if nil != err {
   771  		return
   772  	}
   773  
   774  	snapShotIDType, _, _ := vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber))
   775  	if headhunter.SnapShotIDTypeLive != snapShotIDType {
   776  		err = fmt.Errorf("Write() on non-LiveView fileInodeNumber not allowed")
   777  		return
   778  	}
   779  
   780  	fileInode, err := vS.fetchInodeType(fileInodeNumber, FileType)
   781  	if nil != err {
   782  		logger.ErrorWithError(err)
   783  		return
   784  	}
   785  
   786  	// writes of length 0 succeed but do not change mtime
   787  	if len(buf) == 0 {
   788  		return
   789  	}
   790  
   791  	fileInode.dirty = true
   792  
   793  	logSegmentNumber, logSegmentOffset, err := vS.doSendChunk(fileInode, buf)
   794  	if nil != err {
   795  		logger.ErrorWithError(err)
   796  		return
   797  	}
   798  
   799  	length := uint64(len(buf))
   800  	startingSize := fileInode.Size
   801  
   802  	err = recordWrite(fileInode, offset, length, logSegmentNumber, logSegmentOffset)
   803  	if nil != err {
   804  		logger.ErrorWithError(err)
   805  		return
   806  	}
   807  
   808  	appendedBytes := fileInode.Size - startingSize
   809  	overwrittenBytes := length - appendedBytes
   810  
   811  	stats.IncrementOperationsBucketedBytesAndAppendedOverwritten(stats.FileWrite, length, appendedBytes, overwrittenBytes)
   812  
   813  	updateTime := time.Now()
   814  	fileInode.AttrChangeTime = updateTime
   815  	fileInode.ModificationTime = updateTime
   816  	fileInode.NumWrites++
   817  
   818  	return
   819  }
   820  
   821  func (vS *volumeStruct) Wrote(fileInodeNumber InodeNumber, containerName string, objectName string, fileOffset []uint64, objectOffset []uint64, length []uint64, patchOnly bool) (err error) {
   822  	err = enforceRWMode(false)
   823  	if nil != err {
   824  		return
   825  	}
   826  
   827  	if (len(fileOffset) != len(objectOffset)) || (len(objectOffset) != len(length)) {
   828  		err = fmt.Errorf("Wrote() called with unequal # of fileOffset's (%d), objectOffset's (%d), and length's (%d)", len(fileOffset), len(objectOffset), len(length))
   829  		return
   830  	}
   831  
   832  	snapShotIDType, _, _ := vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber))
   833  	if headhunter.SnapShotIDTypeLive != snapShotIDType {
   834  		err = fmt.Errorf("Wrote() on non-LiveView fileInodeNumber not allowed")
   835  		return
   836  	}
   837  
   838  	fileInode, err := vS.fetchInodeType(fileInodeNumber, FileType)
   839  	if err != nil {
   840  		logger.ErrorWithError(err)
   841  		return err
   842  	}
   843  
   844  	if fileInode.dirty {
   845  		err = flush(fileInode, false)
   846  		if nil != err {
   847  			logger.ErrorWithError(err)
   848  			return
   849  		}
   850  	}
   851  
   852  	logSegmentNumber, err := strconv.ParseUint(objectName, 16, 64)
   853  	if err != nil {
   854  		return
   855  	}
   856  
   857  	err = fileInode.volume.setLogSegmentContainer(logSegmentNumber, containerName)
   858  	if nil != err {
   859  		return
   860  	}
   861  
   862  	fileInode.dirty = true
   863  
   864  	if !patchOnly {
   865  		err = setSizeInMemory(fileInode, 0)
   866  		if err != nil {
   867  			logger.ErrorWithError(err)
   868  			return
   869  		}
   870  	}
   871  
   872  	bytesWritten := uint64(0)
   873  
   874  	for i, thisLength := range length {
   875  		if 0 < thisLength {
   876  			err = recordWrite(fileInode, fileOffset[i], thisLength, logSegmentNumber, objectOffset[i])
   877  			if err != nil {
   878  				logger.ErrorWithError(err)
   879  				return
   880  			}
   881  
   882  			fileInode.NumWrites++
   883  			bytesWritten += thisLength
   884  		}
   885  	}
   886  
   887  	if !patchOnly {
   888  		// For this case only, make it appear we did precisely one write
   889  
   890  		fileInode.NumWrites = 1
   891  	}
   892  
   893  	err = fileInode.volume.flushInode(fileInode)
   894  	if err != nil {
   895  		logger.ErrorWithError(err)
   896  		return
   897  	}
   898  
   899  	stats.IncrementOperationsAndBucketedBytes(stats.FileWrote, bytesWritten)
   900  
   901  	return
   902  }
   903  
   904  func (vS *volumeStruct) SetSize(fileInodeNumber InodeNumber, size uint64) (err error) {
   905  	err = enforceRWMode(false)
   906  	if nil != err {
   907  		return
   908  	}
   909  
   910  	snapShotIDType, _, _ := vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber))
   911  	if headhunter.SnapShotIDTypeLive != snapShotIDType {
   912  		err = fmt.Errorf("SetSize() on non-LiveView fileInodeNumber not allowed")
   913  		return
   914  	}
   915  
   916  	fileInode, err := vS.fetchInodeType(fileInodeNumber, FileType)
   917  	if nil != err {
   918  		return err
   919  	}
   920  
   921  	fileInode.dirty = true
   922  
   923  	err = setSizeInMemory(fileInode, size)
   924  	if nil != err {
   925  		logger.ErrorWithError(err)
   926  		return
   927  	}
   928  
   929  	// changing the file's size is just like a write
   930  	fileInode.NumWrites++
   931  
   932  	err = fileInode.volume.flushInode(fileInode)
   933  	if nil != err {
   934  		logger.ErrorWithError(err)
   935  		return err
   936  	}
   937  
   938  	stats.IncrementOperations(&stats.DirSetsizeOps)
   939  
   940  	return
   941  }
   942  
   943  func (vS *volumeStruct) Flush(fileInodeNumber InodeNumber, andPurge bool) (err error) {
   944  	err = enforceRWMode(false)
   945  	if nil != err {
   946  		return
   947  	}
   948  
   949  	fileInode, ok, err := vS.fetchInode(fileInodeNumber)
   950  	if nil != err {
   951  		// this indicates disk corruption or software bug
   952  		// (err includes volume name and inode number)
   953  		logger.ErrorfWithError(err, "%s: request to flush inode %d volume '%s' failed",
   954  			utils.GetFnName(), fileInodeNumber, vS.volumeName)
   955  		return
   956  	}
   957  	if !ok {
   958  		// this can happen if background flush loses a race with unlink()
   959  		logger.Infof("%s: request to flush free inode %d volume '%s' ignored",
   960  			utils.GetFnName(), fileInodeNumber, vS.volumeName)
   961  		return
   962  	}
   963  	if fileInode.InodeType != FileType {
   964  		// this should never happen unless there's disk corruption
   965  		logger.Errorf("%s: request to flush inode %d volume '%s' type '%v' ignored",
   966  			utils.GetFnName(), fileInodeNumber, vS.volumeName, fileInode.InodeType)
   967  		return
   968  	}
   969  
   970  	if fileInode.dirty {
   971  		err = flush(fileInode, andPurge)
   972  		if nil != err {
   973  			logger.ErrorWithError(err)
   974  			return
   975  		}
   976  	}
   977  
   978  	stats.IncrementOperations(&stats.FileFlushOps)
   979  
   980  	return
   981  }
   982  
   983  func flush(fileInode *inMemoryInodeStruct, andPurge bool) (err error) {
   984  	vS := fileInode.volume
   985  	err = vS.flushInode(fileInode)
   986  	if nil != err {
   987  		logger.ErrorfWithError(err, "flushInode(fileInode) failed")
   988  	}
   989  
   990  	if andPurge {
   991  		var ok bool
   992  		ok, err = vS.inodeCacheDrop(fileInode)
   993  		if nil != err {
   994  			return
   995  		}
   996  		if !ok {
   997  			err = fmt.Errorf("inodeCacheDrop(fileInode) failed")
   998  			return
   999  		}
  1000  	}
  1001  
  1002  	return
  1003  }
  1004  
  1005  func (vS *volumeStruct) resetFileInodeInMemory(fileInode *inMemoryInodeStruct) (err error) {
  1006  	var (
  1007  		fileInodeExtentMap sortedmap.BPlusTree
  1008  		ok                 bool
  1009  	)
  1010  
  1011  	fileInode.dirty = true
  1012  	fileInodeExtentMap = fileInode.payload.(sortedmap.BPlusTree)
  1013  
  1014  	ok = true
  1015  	for ok {
  1016  		ok, err = fileInodeExtentMap.DeleteByIndex(0)
  1017  		if nil != err {
  1018  			err = fmt.Errorf("resetFileInodeInMemory() on Inode# 0x%016X failed: %v", fileInode.InodeNumber, err)
  1019  			return
  1020  		}
  1021  	}
  1022  
  1023  	fileInode.LogSegmentMap = make(map[uint64]uint64)
  1024  	fileInode.Size = 0
  1025  	fileInode.NumWrites = 0
  1026  
  1027  	err = nil
  1028  	return
  1029  }
  1030  
  1031  func (vS *volumeStruct) Coalesce(destInodeNumber InodeNumber, metaDataName string, metaData []byte, elements []*CoalesceElement) (attrChangeTime time.Time, modificationTime time.Time, numWrites uint64, fileSize uint64, err error) {
  1032  	var (
  1033  		alreadyInInodeMap                  bool
  1034  		coalesceTime                       time.Time
  1035  		destInode                          *inMemoryInodeStruct
  1036  		destInodeExtentMap                 sortedmap.BPlusTree
  1037  		destInodeOffsetBeforeElementAppend uint64
  1038  		dirEntryInodeNumber                InodeNumber
  1039  		element                            *CoalesceElement
  1040  		elementInode                       *inMemoryInodeStruct
  1041  		elementInodeExtent                 *fileExtentStruct
  1042  		elementInodeExtentAsValue          sortedmap.Value
  1043  		elementInodeExtentMap              sortedmap.BPlusTree
  1044  		elementInodeExtentMapIndex         int
  1045  		elementInodeExtentMapLen           int
  1046  		inodeList                          []*inMemoryInodeStruct
  1047  		inodeMap                           map[InodeNumber]*inMemoryInodeStruct
  1048  		localErr                           error
  1049  		logSegmentReferencedBytes          uint64
  1050  		ok                                 bool
  1051  		snapShotIDType                     headhunter.SnapShotIDType
  1052  	)
  1053  
  1054  	err = enforceRWMode(false)
  1055  	if nil != err {
  1056  		return
  1057  	}
  1058  
  1059  	// Validate all referenced {Dir|File}Inodes
  1060  
  1061  	inodeMap = make(map[InodeNumber]*inMemoryInodeStruct)
  1062  	inodeList = make([]*inMemoryInodeStruct, 0, 1+len(elements))
  1063  
  1064  	snapShotIDType, _, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(destInodeNumber))
  1065  	if headhunter.SnapShotIDTypeLive != snapShotIDType {
  1066  		err = blunder.NewError(blunder.PermDeniedError, "Coalesce into non-LiveView destInodeNumber 0x%016X not allowed", destInodeNumber)
  1067  		return
  1068  	}
  1069  
  1070  	destInode, ok, err = vS.fetchInode(destInodeNumber)
  1071  	if nil != err {
  1072  		err = blunder.NewError(blunder.BadFileError, "Coalesce() couldn't fetch destInodeNumber 0x%016X: %v", destInodeNumber, err)
  1073  		return
  1074  	}
  1075  	if !ok {
  1076  		err = blunder.NewError(blunder.NotFoundError, "Coalesce() couldn't find destInodeNumber 0x%16X", destInodeNumber)
  1077  		return
  1078  	}
  1079  	if destInode.InodeType != FileType {
  1080  		err = blunder.NewError(blunder.PermDeniedError, "Coalesce() called for destInodeNumber 0x%016X that is not a FileInode", destInodeNumber)
  1081  		return
  1082  	}
  1083  
  1084  	inodeMap[destInodeNumber] = destInode
  1085  	inodeList = append(inodeList, destInode)
  1086  
  1087  	for _, element = range elements {
  1088  		snapShotIDType, _, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(element.ElementInodeNumber))
  1089  		if headhunter.SnapShotIDTypeLive != snapShotIDType {
  1090  			err = blunder.NewError(blunder.PermDeniedError, "Coalesce() from non-LiveView element.ElementInodeNumber (0x%016X) not allowed", element.ElementInodeNumber)
  1091  			return
  1092  		}
  1093  
  1094  		_, alreadyInInodeMap = inodeMap[element.ElementInodeNumber]
  1095  		if alreadyInInodeMap {
  1096  			err = blunder.NewError(blunder.InvalidArgError, "Coalesce() called with duplicate Element Inode 0x%016X", element.ElementInodeNumber)
  1097  			return
  1098  		}
  1099  
  1100  		elementInode, ok, err = vS.fetchInode(element.ElementInodeNumber)
  1101  		if nil != err {
  1102  			err = blunder.NewError(blunder.BadFileError, "Coalesce() couldn't fetch ElementInodeNumber 0x%016X: %v", element.ElementInodeNumber, err)
  1103  			return
  1104  		}
  1105  
  1106  		inodeMap[element.ElementInodeNumber] = elementInode
  1107  		inodeList = append(inodeList, elementInode)
  1108  
  1109  		if !ok {
  1110  			err = blunder.NewError(blunder.NotFoundError, "Coalesce() couldn't find ElementInodeNumber 0x%16X", element.ElementInodeNumber)
  1111  			return
  1112  		}
  1113  		if elementInode.InodeType != FileType {
  1114  			err = blunder.NewError(blunder.PermDeniedError, "Coalesce() called for ElementInodeNumber 0x%016X that is not a FileInode", element.ElementInodeNumber)
  1115  			return
  1116  		}
  1117  		if elementInode.LinkCount != 1 {
  1118  			err = blunder.NewError(blunder.TooManyLinksError, "Coalesce() called for ElementInodeNumber 0x%016X with LinkCount not == 1 (%v)", element.ElementInodeNumber, elementInode.LinkCount)
  1119  			return
  1120  		}
  1121  
  1122  		dirEntryInodeNumber, err = vS.lookupByDirInodeNumber(element.ContainingDirectoryInodeNumber, element.ElementName)
  1123  		if nil != err {
  1124  			err = blunder.NewError(blunder.InvalidArgError, "Coalesce() called for ElementName %s not found in ContainingDir 0x%016X: %v", element.ElementName, element.ContainingDirectoryInodeNumber, err)
  1125  			return
  1126  		}
  1127  		if dirEntryInodeNumber != element.ElementInodeNumber {
  1128  			err = blunder.NewError(blunder.InvalidArgError, "Coalesce() called for ElementName %s in ContainingDir 0x%016X had mismatched InodeNumber", element.ElementName, element.ContainingDirectoryInodeNumber)
  1129  			return
  1130  		}
  1131  	}
  1132  
  1133  	// Ensure all referenced FileInodes are pre-flushed
  1134  
  1135  	err = vS.flushInodes(inodeList)
  1136  	if nil != err {
  1137  		err = blunder.NewError(blunder.InvalidArgError, "Coalesce() unable to flush inodeList: %v", err)
  1138  		return
  1139  	}
  1140  
  1141  	// Now "append" each Element's extents to destInode (creating duplicate references to LogSegments for now)
  1142  
  1143  	destInodeExtentMap = destInode.payload.(sortedmap.BPlusTree)
  1144  
  1145  	destInode.dirty = true
  1146  
  1147  	destInodeOffsetBeforeElementAppend = fileLen(destInodeExtentMap)
  1148  
  1149  	for _, element = range elements {
  1150  		elementInode = inodeMap[element.ElementInodeNumber]
  1151  		destInode.NumWrites++
  1152  		elementInodeExtentMap = elementInode.payload.(sortedmap.BPlusTree)
  1153  		elementInodeExtentMapLen, err = elementInodeExtentMap.Len()
  1154  		for elementInodeExtentMapIndex = 0; elementInodeExtentMapIndex < elementInodeExtentMapLen; elementInodeExtentMapIndex++ {
  1155  			_, elementInodeExtentAsValue, ok, err = elementInodeExtentMap.GetByIndex(elementInodeExtentMapIndex)
  1156  			if nil != err {
  1157  				localErr = vS.resetFileInodeInMemory(destInode)
  1158  				if nil != localErr {
  1159  					logger.Fatalf("Coalesce() doing resetFileInodeInMemory(destInode) failed: %v", localErr)
  1160  				}
  1161  				localErr = vS.flushInode(destInode)
  1162  				if nil != localErr {
  1163  					logger.Errorf("Coalesce() doing flushInode(destInode) failed: %v", localErr)
  1164  				}
  1165  				err = blunder.NewError(blunder.InvalidArgError, "Coalesce() unable to fetch fileExtentStruct from ExtentMap: %v", err)
  1166  				return
  1167  			}
  1168  			elementInodeExtent = elementInodeExtentAsValue.(*fileExtentStruct)
  1169  			elementInodeExtent.FileOffset += destInodeOffsetBeforeElementAppend
  1170  			_, err = destInodeExtentMap.Put(elementInodeExtent.FileOffset, elementInodeExtent)
  1171  			if nil != err {
  1172  				localErr = vS.resetFileInodeInMemory(destInode)
  1173  				if nil != localErr {
  1174  					logger.Fatalf("Coalesce() doing resetFileInodeInMemory(destInode) failed: %v", localErr)
  1175  				}
  1176  				localErr = vS.flushInode(destInode)
  1177  				if nil != localErr {
  1178  					logger.Errorf("Coalesce() doing flushInode(destInode) failed: %v", localErr)
  1179  				}
  1180  				err = blunder.NewError(blunder.InvalidArgError, "Coalesce() unable to append elementInodeExtent to destInodeExtentMap: %v", err)
  1181  				return
  1182  			}
  1183  			logSegmentReferencedBytes, ok = destInode.LogSegmentMap[elementInodeExtent.LogSegmentNumber]
  1184  			if ok {
  1185  				destInode.LogSegmentMap[elementInodeExtent.LogSegmentNumber] = elementInodeExtent.Length + logSegmentReferencedBytes
  1186  			} else {
  1187  				destInode.LogSegmentMap[elementInodeExtent.LogSegmentNumber] = elementInodeExtent.Length
  1188  			}
  1189  		}
  1190  		destInodeOffsetBeforeElementAppend += elementInode.Size
  1191  		err = setSizeInMemory(destInode, destInodeOffsetBeforeElementAppend)
  1192  		if nil != err {
  1193  			localErr = vS.resetFileInodeInMemory(destInode)
  1194  			if nil != localErr {
  1195  				logger.Fatalf("Coalesce() doing resetFileInodeInMemory(destInode) failed: %v", localErr)
  1196  			}
  1197  			localErr = vS.flushInode(destInode)
  1198  			if nil != localErr {
  1199  				logger.Errorf("Coalesce() doing flushInode(destInode) failed: %v", localErr)
  1200  			}
  1201  			err = blunder.NewError(blunder.InvalidArgError, "Coalesce() unable to setSize() destInodeNumber 0x%016X: %v", destInodeNumber, err)
  1202  			return
  1203  		}
  1204  	}
  1205  
  1206  	// Now, destInode is fully assembled... update its metadata & assemble remaining results
  1207  	coalesceTime = time.Now()
  1208  	destInode.CreationTime = coalesceTime
  1209  	destInode.AttrChangeTime = coalesceTime
  1210  	destInode.ModificationTime = coalesceTime
  1211  
  1212  	// attach new middleware headers (why are we making a copy?)
  1213  	inodeStreamBuf := make([]byte, len(metaData))
  1214  	copy(inodeStreamBuf, metaData)
  1215  	destInode.StreamMap[metaDataName] = inodeStreamBuf
  1216  
  1217  	// collect the NumberOfWrites value while locked (important for Etag)
  1218  	numWrites = destInode.NumWrites
  1219  	fileSize = destInode.Size
  1220  	attrChangeTime = destInode.AttrChangeTime
  1221  	modificationTime = destInode.ModificationTime
  1222  
  1223  	// Now, destInode is fully assembled... need to remove all elements references to currently shared LogSegments
  1224  
  1225  	for _, element = range elements {
  1226  		localErr = vS.resetFileInodeInMemory(inodeMap[element.ElementInodeNumber])
  1227  		if nil != err {
  1228  			logger.Fatalf("Coalesce() doing resetFileInodeInMemory(inodeMap[element.ElementInodeNumber]) failed: %v", localErr)
  1229  		}
  1230  	}
  1231  
  1232  	// Time to flush all affected FileInodes
  1233  
  1234  	err = vS.flushInodes(inodeList)
  1235  	if nil != err {
  1236  		err = fmt.Errorf("Coalesce() doing flushInodes(inodeList) failed: %v", err)
  1237  		return
  1238  	}
  1239  
  1240  	// Now we can Unlink and Destroy each element
  1241  
  1242  	for _, element = range elements {
  1243  		err = vS.Unlink(element.ContainingDirectoryInodeNumber, element.ElementName, false)
  1244  		if nil != err {
  1245  			err = fmt.Errorf("Coalesce() doing Unlink(element.ContainingDirectoryInodeNumber, element.ElementName, false) failed: %v", err)
  1246  			return
  1247  		}
  1248  		err = vS.Destroy(element.ElementInodeNumber)
  1249  		if nil != err {
  1250  			err = fmt.Errorf("Coalesce() doing Destroy(element.ElementInodeNumber) failed: %v", err)
  1251  			return
  1252  		}
  1253  	}
  1254  
  1255  	// All done
  1256  
  1257  	err = nil
  1258  	return
  1259  }
  1260  
  1261  func (vS *volumeStruct) DefragmentFile(fileInodeNumber InodeNumber, startingFileOffset uint64, chunkSize uint64) (nextFileOffset uint64, eofReached bool, err error) {
  1262  	var (
  1263  		chunk           []byte
  1264  		chunkSizeCapped uint64
  1265  		fileInode       *inMemoryInodeStruct
  1266  	)
  1267  
  1268  	err = enforceRWMode(false)
  1269  	if nil != err {
  1270  		return
  1271  	}
  1272  
  1273  	fileInode, err = vS.fetchInodeType(fileInodeNumber, FileType)
  1274  	if nil != err {
  1275  		return
  1276  	}
  1277  
  1278  	if startingFileOffset >= fileInode.Size {
  1279  		nextFileOffset = fileInode.Size
  1280  		eofReached = true
  1281  		err = nil
  1282  		return
  1283  	}
  1284  
  1285  	if (startingFileOffset + chunkSize) >= fileInode.Size {
  1286  		chunkSizeCapped = fileInode.Size - startingFileOffset
  1287  		eofReached = true
  1288  	} else {
  1289  		chunkSizeCapped = chunkSize
  1290  		eofReached = false
  1291  	}
  1292  
  1293  	nextFileOffset = startingFileOffset + chunkSizeCapped
  1294  
  1295  	chunk, err = vS.Read(fileInodeNumber, startingFileOffset, chunkSizeCapped, nil)
  1296  	if nil != err {
  1297  		return
  1298  	}
  1299  
  1300  	err = vS.Write(fileInodeNumber, startingFileOffset, chunk, nil)
  1301  
  1302  	return // err as returned by Write() is sufficient
  1303  }
  1304  
  1305  func (vS *volumeStruct) setLogSegmentContainer(logSegmentNumber uint64, containerName string) (err error) {
  1306  	containerNameAsByteSlice := utils.StringToByteSlice(containerName)
  1307  	err = vS.headhunterVolumeHandle.PutLogSegmentRec(logSegmentNumber, containerNameAsByteSlice)
  1308  	return
  1309  }
  1310  
  1311  func (vS *volumeStruct) getLogSegmentContainer(logSegmentNumber uint64) (containerName string, err error) {
  1312  	containerNameAsByteSlice, err := vS.headhunterVolumeHandle.GetLogSegmentRec(logSegmentNumber)
  1313  	if nil != err {
  1314  		return
  1315  	}
  1316  	containerName = utils.ByteSliceToString(containerNameAsByteSlice)
  1317  	return
  1318  }
  1319  
  1320  func (vS *volumeStruct) getObjectLocationFromLogSegmentNumber(logSegmentNumber uint64) (containerName string, objectName string, objectPath string, err error) {
  1321  	var (
  1322  		nonce uint64
  1323  	)
  1324  
  1325  	containerName, err = vS.getLogSegmentContainer(logSegmentNumber)
  1326  	if nil != err {
  1327  		return
  1328  	}
  1329  
  1330  	_, _, nonce = vS.headhunterVolumeHandle.SnapShotU64Decode(logSegmentNumber)
  1331  
  1332  	objectName = fmt.Sprintf("%016X", nonce)
  1333  	objectPath = fmt.Sprintf("/v1/%s/%s/%016X", vS.accountName, containerName, nonce)
  1334  	return
  1335  }