github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/inode/file.go (about)

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