github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/data/block_types.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package data
     6  
     7  import (
     8  	"fmt"
     9  	"strconv"
    10  	"sync"
    11  
    12  	"github.com/keybase/client/go/kbfs/kbfshash"
    13  	"github.com/keybase/go-codec/codec"
    14  )
    15  
    16  // Int64Offset represents the offset of a block within a file.
    17  type Int64Offset int64
    18  
    19  var _ Offset = Int64Offset(0)
    20  
    21  // Equals implements the Offset interface for Int64Offset.
    22  func (i Int64Offset) Equals(other Offset) bool {
    23  	otherI, ok := other.(Int64Offset)
    24  	if !ok {
    25  		panic(fmt.Sprintf("Can't compare against non-int offset: %T", other))
    26  	}
    27  	return int64(i) == int64(otherI)
    28  }
    29  
    30  // Less implements the Offset interface for Int64Offset.
    31  func (i Int64Offset) Less(other Offset) bool {
    32  	otherI, ok := other.(Int64Offset)
    33  	if !ok {
    34  		panic(fmt.Sprintf("Can't compare against non-int offset: %T", other))
    35  	}
    36  	return int64(i) < int64(otherI)
    37  }
    38  
    39  func (i Int64Offset) String() string {
    40  	return strconv.FormatInt(int64(i), 10)
    41  }
    42  
    43  // StringOffset represents the offset of a block within a directory.
    44  type StringOffset string
    45  
    46  var _ Offset = (*StringOffset)(nil)
    47  
    48  // Equals implements the Offset interface for StringOffset.
    49  func (s *StringOffset) Equals(other Offset) bool {
    50  	if s == nil {
    51  		return other == nil
    52  	} else if other == nil {
    53  		return false
    54  	}
    55  	otherS, ok := other.(*StringOffset)
    56  	if !ok {
    57  		panic(fmt.Sprintf("Can't compare against non-string offset: %T", other))
    58  	}
    59  	return string(*s) == string(*otherS)
    60  }
    61  
    62  // Less implements the Offset interface for StringOffset.
    63  func (s *StringOffset) Less(other Offset) bool {
    64  	if s == nil {
    65  		return other != nil
    66  	} else if other == nil {
    67  		return false
    68  	}
    69  	otherS, ok := other.(*StringOffset)
    70  	if !ok {
    71  		panic(fmt.Sprintf("Can't compare against non-string offset: %T", other))
    72  	}
    73  	return string(*s) < string(*otherS)
    74  }
    75  
    76  func (s *StringOffset) String() string {
    77  	return string(*s)
    78  }
    79  
    80  // IndirectDirPtr pairs an indirect dir block with the start of that
    81  // block's range of directory entries (inclusive)
    82  type IndirectDirPtr struct {
    83  	// TODO: Make sure that the block is not dirty when the EncodedSize
    84  	// field is non-zero.
    85  	BlockInfo
    86  	Off StringOffset `codec:"o"`
    87  
    88  	codec.UnknownFieldSetHandler
    89  }
    90  
    91  // IndirectFilePtr pairs an indirect file block with the start of that
    92  // block's range of bytes (inclusive)
    93  //
    94  // If `Holes` is true, then this pointer is part of a list of pointers
    95  // that has non-continuous offsets; that is, the offset of ptr `i`
    96  // plus the length of the corresponding block contents is less than
    97  // the offset of ptr `i`+1.
    98  type IndirectFilePtr struct {
    99  	// When the EncodedSize field is non-zero, the block must not
   100  	// be dirty.
   101  	BlockInfo
   102  	Off Int64Offset `codec:"o"`
   103  	// Marker for files with holes.  This is here for historical
   104  	// reasons; a `FileBlock` should be treated as having a `HasHoles`
   105  	// flag set to true if any of its IPtrs have `Holes` set to true.
   106  	Holes bool `codec:"h,omitempty"`
   107  
   108  	codec.UnknownFieldSetHandler
   109  }
   110  
   111  // CommonBlock holds block data that is common for both subdirectories
   112  // and files.
   113  type CommonBlock struct {
   114  	// IsInd indicates where this block is so big it requires indirect pointers
   115  	IsInd bool `codec:"s"`
   116  
   117  	codec.UnknownFieldSetHandler
   118  
   119  	cacheMtx sync.RWMutex
   120  	// cachedEncodedSize is the locally-cached (non-serialized)
   121  	// encoded size for this block.
   122  	cachedEncodedSize uint32
   123  }
   124  
   125  var _ Block = (*CommonBlock)(nil)
   126  
   127  // GetEncodedSize implements the Block interface for CommonBlock
   128  func (cb *CommonBlock) GetEncodedSize() uint32 {
   129  	cb.cacheMtx.RLock()
   130  	defer cb.cacheMtx.RUnlock()
   131  	return cb.cachedEncodedSize
   132  }
   133  
   134  // SetEncodedSize implements the Block interface for CommonBlock
   135  func (cb *CommonBlock) SetEncodedSize(size uint32) {
   136  	cb.cacheMtx.Lock()
   137  	defer cb.cacheMtx.Unlock()
   138  	cb.cachedEncodedSize = size
   139  }
   140  
   141  // DataVersion returns data version for this block.
   142  func (cb *CommonBlock) DataVersion() Ver {
   143  	return FirstValidVer
   144  }
   145  
   146  // NewEmpty implements the Block interface for CommonBlock.
   147  func (cb *CommonBlock) NewEmpty() Block {
   148  	return NewCommonBlock()
   149  }
   150  
   151  // NewEmptier implements the Block interface for CommonBlock.
   152  func (cb *CommonBlock) NewEmptier() func() Block {
   153  	return NewCommonBlock
   154  }
   155  
   156  // ToCommonBlock implements the Block interface for CommonBlock.
   157  func (cb *CommonBlock) ToCommonBlock() *CommonBlock {
   158  	return cb
   159  }
   160  
   161  // IsIndirect implements the Block interface for CommonBlock.
   162  func (cb *CommonBlock) IsIndirect() bool {
   163  	return cb.IsInd
   164  }
   165  
   166  // IsTail implements the Block interface for CommonBlock.
   167  func (cb *CommonBlock) IsTail() bool {
   168  	panic("CommonBlock doesn't know how to compute IsTail")
   169  }
   170  
   171  // OffsetExceedsData implements the Block interface for CommonBlock.
   172  func (cb *CommonBlock) OffsetExceedsData(_, _ Offset) bool {
   173  	panic("CommonBlock doesn't implement data methods")
   174  }
   175  
   176  // Set implements the Block interface for CommonBlock.
   177  func (cb *CommonBlock) Set(other Block) {
   178  	otherCommon := other.ToCommonBlock()
   179  	cb.IsInd = otherCommon.IsInd
   180  	cb.UnknownFieldSetHandler = otherCommon.UnknownFieldSetHandler
   181  	cb.SetEncodedSize(otherCommon.GetEncodedSize())
   182  }
   183  
   184  // BytesCanBeDirtied implements the Block interface for CommonBlock.
   185  func (cb *CommonBlock) BytesCanBeDirtied() int64 {
   186  	return 0
   187  }
   188  
   189  // DeepCopy copies a CommonBlock without the lock.
   190  func (cb *CommonBlock) DeepCopy() CommonBlock {
   191  	return CommonBlock{
   192  		IsInd: cb.IsInd,
   193  		// We don't need to copy UnknownFieldSetHandler because it's immutable.
   194  		UnknownFieldSetHandler: cb.UnknownFieldSetHandler,
   195  		cachedEncodedSize:      cb.GetEncodedSize(),
   196  	}
   197  }
   198  
   199  // NewCommonBlock returns a generic block, unsuitable for caching.
   200  func NewCommonBlock() Block {
   201  	return &CommonBlock{}
   202  }
   203  
   204  // NewCommonBlockForTesting returns a common block with some of the
   205  // internal state set, which is useful for testing.
   206  func NewCommonBlockForTesting(
   207  	isInd bool, cachedEncodedSize uint32) CommonBlock {
   208  	return CommonBlock{
   209  		IsInd:             isInd,
   210  		cachedEncodedSize: cachedEncodedSize,
   211  	}
   212  }
   213  
   214  // DirBlock is the contents of a directory
   215  type DirBlock struct {
   216  	CommonBlock
   217  	// if not indirect, a map of path name to directory entry
   218  	Children map[string]DirEntry `codec:"c,omitempty"`
   219  	// if indirect, contains the indirect pointers to the next level of blocks
   220  	IPtrs []IndirectDirPtr `codec:"i,omitempty"`
   221  }
   222  
   223  var _ BlockWithPtrs = (*DirBlock)(nil)
   224  
   225  // NewDirBlock creates a new, empty DirBlock.
   226  func NewDirBlock() Block {
   227  	return &DirBlock{
   228  		Children: make(map[string]DirEntry),
   229  	}
   230  }
   231  
   232  // NewDirBlockWithPtrs creates a new, empty DirBlock.
   233  func NewDirBlockWithPtrs(isInd bool) BlockWithPtrs {
   234  	db := NewDirBlock().(*DirBlock)
   235  	db.IsInd = isInd
   236  	return db
   237  }
   238  
   239  // NewEmpty implements the Block interface for DirBlock
   240  func (db *DirBlock) NewEmpty() Block {
   241  	return NewDirBlock()
   242  }
   243  
   244  // NewEmptier implements the Block interface for DirBlock.
   245  func (db *DirBlock) NewEmptier() func() Block {
   246  	return NewDirBlock
   247  }
   248  
   249  // IsTail implements the Block interface for DirBlock.
   250  func (db *DirBlock) IsTail() bool {
   251  	if db.IsInd {
   252  		return len(db.IPtrs) == 0
   253  	}
   254  	for _, de := range db.Children {
   255  		if de.Type != Sym {
   256  			return false
   257  		}
   258  	}
   259  	return true
   260  }
   261  
   262  // DataVersion returns data version for this block, which is assumed
   263  // to have been modified locally.
   264  func (db *DirBlock) DataVersion() Ver {
   265  	if db.IsInd {
   266  		return IndirectDirsVer
   267  	}
   268  	return FirstValidVer
   269  }
   270  
   271  // ToCommonBlock implements the Block interface for DirBlock.
   272  func (db *DirBlock) ToCommonBlock() *CommonBlock {
   273  	return &db.CommonBlock
   274  }
   275  
   276  // Set implements the Block interface for DirBlock
   277  func (db *DirBlock) Set(other Block) {
   278  	otherDb := other.(*DirBlock)
   279  	dbCopy := otherDb.DeepCopy()
   280  	db.Children = dbCopy.Children
   281  	db.IPtrs = dbCopy.IPtrs
   282  	db.ToCommonBlock().Set(dbCopy.ToCommonBlock())
   283  }
   284  
   285  // DeepCopy makes a complete copy of a DirBlock
   286  func (db *DirBlock) DeepCopy() *DirBlock {
   287  	childrenCopy := make(map[string]DirEntry, len(db.Children))
   288  	for k, v := range db.Children {
   289  		childrenCopy[k] = v
   290  	}
   291  	var iptrsCopy []IndirectDirPtr
   292  	if db.IsInd {
   293  		iptrsCopy = make([]IndirectDirPtr, len(db.IPtrs))
   294  		copy(iptrsCopy, db.IPtrs)
   295  	}
   296  	return &DirBlock{
   297  		CommonBlock: db.CommonBlock.DeepCopy(),
   298  		Children:    childrenCopy,
   299  		IPtrs:       iptrsCopy,
   300  	}
   301  }
   302  
   303  // OffsetExceedsData implements the Block interface for DirBlock.
   304  func (db *DirBlock) OffsetExceedsData(startOff, off Offset) bool {
   305  	// DirBlocks have open-ended children maps, so theoretically this
   306  	// block could have children all the way to the end of the
   307  	// alphabet.
   308  	return false
   309  }
   310  
   311  // BytesCanBeDirtied implements the Block interface for DirBlock.
   312  func (db *DirBlock) BytesCanBeDirtied() int64 {
   313  	// Dir blocks don't track individual dirty bytes.
   314  	return 0
   315  }
   316  
   317  // FirstOffset implements the Block interface for DirBlock.
   318  func (db *DirBlock) FirstOffset() Offset {
   319  	firstString := StringOffset("")
   320  	return &firstString
   321  }
   322  
   323  // NumIndirectPtrs implements the BlockWithPtrs interface for DirBlock.
   324  func (db *DirBlock) NumIndirectPtrs() int {
   325  	if !db.IsInd {
   326  		panic("NumIndirectPtrs called on a direct directory block")
   327  	}
   328  	return len(db.IPtrs)
   329  }
   330  
   331  // IndirectPtr implements the BlockWithPtrs interface for DirBlock.
   332  func (db *DirBlock) IndirectPtr(i int) (BlockInfo, Offset) {
   333  	if !db.IsInd {
   334  		panic("IndirectPtr called on a direct directory block")
   335  	}
   336  	iptr := db.IPtrs[i]
   337  	off := iptr.Off
   338  	return iptr.BlockInfo, &off
   339  }
   340  
   341  // AppendNewIndirectPtr implements the BlockWithPtrs interface for FileBlock.
   342  func (db *DirBlock) AppendNewIndirectPtr(ptr BlockPointer, off Offset) {
   343  	if !db.IsInd {
   344  		panic("AppendNewIndirectPtr called on a direct directory block")
   345  	}
   346  	sOff, ok := off.(*StringOffset)
   347  	if !ok {
   348  		panic(fmt.Sprintf("AppendNewIndirectPtr called on a directory block "+
   349  			"with a %T offset", off))
   350  	}
   351  	db.IPtrs = append(db.IPtrs, IndirectDirPtr{
   352  		BlockInfo: BlockInfo{
   353  			BlockPointer: ptr,
   354  			EncodedSize:  0,
   355  		},
   356  		Off: *sOff,
   357  	})
   358  }
   359  
   360  // ClearIndirectPtrSize implements the BlockWithPtrs interface for DirBlock.
   361  func (db *DirBlock) ClearIndirectPtrSize(i int) {
   362  	if !db.IsInd {
   363  		panic("ClearIndirectPtrSize called on a direct directory block")
   364  	}
   365  	db.IPtrs[i].EncodedSize = 0
   366  }
   367  
   368  // SetIndirectPtrType implements the BlockWithPtrs interface for DirBlock.
   369  func (db *DirBlock) SetIndirectPtrType(i int, dt BlockDirectType) {
   370  	if !db.IsInd {
   371  		panic("SetIndirectPtrType called on a direct directory block")
   372  	}
   373  	db.IPtrs[i].DirectType = dt
   374  }
   375  
   376  // SwapIndirectPtrs implements the BlockWithPtrs interface for DirBlock.
   377  func (db *DirBlock) SwapIndirectPtrs(i int, other BlockWithPtrs, otherI int) {
   378  	otherDB, ok := other.(*DirBlock)
   379  	if !ok {
   380  		panic(fmt.Sprintf(
   381  			"SwapIndirectPtrs cannot swap between block types: %T", other))
   382  	}
   383  
   384  	db.IPtrs[i], otherDB.IPtrs[otherI] = otherDB.IPtrs[otherI], db.IPtrs[i]
   385  }
   386  
   387  // SetIndirectPtrOff implements the BlockWithPtrs interface for DirBlock.
   388  func (db *DirBlock) SetIndirectPtrOff(i int, off Offset) {
   389  	if !db.IsInd {
   390  		panic("SetIndirectPtrOff called on a direct directory block")
   391  	}
   392  	sOff, ok := off.(*StringOffset)
   393  	if !ok {
   394  		panic(fmt.Sprintf("SetIndirectPtrOff called on a dirctory block "+
   395  			"with a %T offset", off))
   396  	}
   397  	db.IPtrs[i].Off = *sOff
   398  }
   399  
   400  // SetIndirectPtrInfo implements the BlockWithPtrs interface for DirBlock.
   401  func (db *DirBlock) SetIndirectPtrInfo(i int, info BlockInfo) {
   402  	if !db.IsInd {
   403  		panic("SetIndirectPtrInfo called on a direct directory block")
   404  	}
   405  	db.IPtrs[i].BlockInfo = info
   406  }
   407  
   408  // TotalPlainSizeEstimate returns an estimate of the plaintext size of
   409  // this directory block.
   410  func (db *DirBlock) TotalPlainSizeEstimate(
   411  	plainSize int, bsplit BlockSplitter) int {
   412  	if !db.IsIndirect() || len(db.IPtrs) == 0 {
   413  		return plainSize
   414  	}
   415  
   416  	// If the top block is indirect, it's too costly to estimate the
   417  	// sizes by checking the plain sizes of all the leafs.  Instead
   418  	// use the following imperfect heuristics:
   419  	//
   420  	// * If there are N child pointers, and the first one is a direct
   421  	//   pointer, assume N-1 of them are full.
   422  	//
   423  	// * If there are N child pointers, and the first one is an
   424  	//   indirect pointer, just give up and max out at the maximum
   425  	//   number of indirect pointers in a block, assuming that at
   426  	//   least one indirect block is full of pointers when there are
   427  	//   at least 2 indirect levels in the tree.
   428  	//
   429  	// This isn't great since it overestimates in many cases
   430  	// (especially when removing entries), and can vastly unerestimate
   431  	// if there are more than 2 levels of indirection.  But it seems
   432  	// unlikely that directory byte size matters for anything in real
   433  	// life.  Famous last words, of course...
   434  	if db.IPtrs[0].DirectType == DirectBlock {
   435  		return MaxBlockSizeBytesDefault * (len(db.IPtrs) - 1)
   436  	}
   437  	return MaxBlockSizeBytesDefault * bsplit.MaxPtrsPerBlock()
   438  }
   439  
   440  // FileBlock is the contents of a file
   441  type FileBlock struct {
   442  	CommonBlock
   443  	// if not indirect, the full contents of this block
   444  	Contents []byte `codec:"c,omitempty"`
   445  	// if indirect, contains the indirect pointers to the next level of blocks
   446  	IPtrs []IndirectFilePtr `codec:"i,omitempty"`
   447  
   448  	// this is used for caching plaintext (block.Contents) hash. It is used by
   449  	// only direct blocks.
   450  	hash *kbfshash.RawDefaultHash
   451  }
   452  
   453  var _ BlockWithPtrs = (*FileBlock)(nil)
   454  
   455  // NewFileBlock creates a new, empty FileBlock.
   456  func NewFileBlock() Block {
   457  	return &FileBlock{
   458  		Contents: make([]byte, 0),
   459  	}
   460  }
   461  
   462  // NewFileBlockWithPtrs creates a new, empty FileBlock.
   463  func NewFileBlockWithPtrs(isInd bool) BlockWithPtrs {
   464  	fb := NewFileBlock().(*FileBlock)
   465  	fb.IsInd = isInd
   466  	return fb
   467  }
   468  
   469  // NewEmpty implements the Block interface for FileBlock
   470  func (fb *FileBlock) NewEmpty() Block {
   471  	return &FileBlock{}
   472  }
   473  
   474  // NewEmptier implements the Block interface for FileBlock.
   475  func (fb *FileBlock) NewEmptier() func() Block {
   476  	return NewFileBlock
   477  }
   478  
   479  // IsTail implements the Block interface for FileBlock.
   480  func (fb *FileBlock) IsTail() bool {
   481  	if fb.IsInd {
   482  		return len(fb.IPtrs) == 0
   483  	}
   484  	return true
   485  }
   486  
   487  // DataVersion returns data version for this block, which is assumed
   488  // to have been modified locally.
   489  func (fb *FileBlock) DataVersion() Ver {
   490  	if !fb.IsInd {
   491  		return FirstValidVer
   492  	}
   493  
   494  	if len(fb.IPtrs) == 0 {
   495  		// This is a truncated file block that hasn't had its level of
   496  		// indirection removed.
   497  		return FirstValidVer
   498  	}
   499  
   500  	// If this is an indirect block, and none of its children are
   501  	// marked as direct blocks, then this must be a big file.  Note
   502  	// that we do it this way, rather than returning on the first
   503  	// non-direct block, to support appending to existing files and
   504  	// making them big.
   505  	hasHoles := false
   506  	hasDirect := false
   507  	maxDirectType := UnknownDirectType
   508  	for i := range fb.IPtrs {
   509  		if maxDirectType != UnknownDirectType &&
   510  			fb.IPtrs[i].DirectType != UnknownDirectType &&
   511  			maxDirectType != fb.IPtrs[i].DirectType {
   512  			panic("Mixed data versions among indirect pointers")
   513  		}
   514  		if fb.IPtrs[i].DirectType > maxDirectType {
   515  			maxDirectType = fb.IPtrs[i].DirectType
   516  		}
   517  
   518  		if fb.IPtrs[i].DirectType == DirectBlock {
   519  			hasDirect = true
   520  		} else if fb.IPtrs[i].Holes {
   521  			hasHoles = true
   522  		}
   523  		// We can only safely break if both vars are definitely set to
   524  		// their final value.
   525  		if hasDirect && hasHoles {
   526  			break
   527  		}
   528  	}
   529  
   530  	if maxDirectType == UnknownDirectType {
   531  		panic("No known type for any indirect pointer")
   532  	}
   533  
   534  	if !hasDirect {
   535  		return AtLeastTwoLevelsOfChildrenVer
   536  	} else if hasHoles {
   537  		return ChildHolesVer
   538  	}
   539  	return FirstValidVer
   540  }
   541  
   542  // ToCommonBlock implements the Block interface for FileBlock.
   543  func (fb *FileBlock) ToCommonBlock() *CommonBlock {
   544  	return &fb.CommonBlock
   545  }
   546  
   547  // Set implements the Block interface for FileBlock
   548  func (fb *FileBlock) Set(other Block) {
   549  	otherFb := other.(*FileBlock)
   550  	fbCopy := otherFb.DeepCopy()
   551  	fb.Contents = fbCopy.Contents
   552  	fb.IPtrs = fbCopy.IPtrs
   553  	fb.ToCommonBlock().Set(fbCopy.ToCommonBlock())
   554  	// Ensure that the Set is complete from Go's perspective by calculating the
   555  	// hash on the new FileBlock if the old one has been set. This is mainly so
   556  	// tests can blindly compare that blocks are equivalent.
   557  	h := func() *kbfshash.RawDefaultHash {
   558  		otherFb.cacheMtx.RLock()
   559  		defer otherFb.cacheMtx.RUnlock()
   560  		return otherFb.hash
   561  	}()
   562  	if h != nil {
   563  		_ = fb.GetHash()
   564  	}
   565  }
   566  
   567  // DeepCopy makes a complete copy of a FileBlock
   568  func (fb *FileBlock) DeepCopy() *FileBlock {
   569  	var contentsCopy []byte
   570  	if fb.Contents != nil {
   571  		contentsCopy = make([]byte, len(fb.Contents))
   572  		copy(contentsCopy, fb.Contents)
   573  	}
   574  	var iptrsCopy []IndirectFilePtr
   575  	if fb.IPtrs != nil {
   576  		iptrsCopy = make([]IndirectFilePtr, len(fb.IPtrs))
   577  		copy(iptrsCopy, fb.IPtrs)
   578  	}
   579  	return &FileBlock{
   580  		CommonBlock: fb.CommonBlock.DeepCopy(),
   581  		Contents:    contentsCopy,
   582  		IPtrs:       iptrsCopy,
   583  	}
   584  }
   585  
   586  // GetHash returns the hash of this FileBlock. If the hash is nil, it first
   587  // calculates it.
   588  func (fb *FileBlock) GetHash() kbfshash.RawDefaultHash {
   589  	h := func() *kbfshash.RawDefaultHash {
   590  		fb.cacheMtx.RLock()
   591  		defer fb.cacheMtx.RUnlock()
   592  		return fb.hash
   593  	}()
   594  	if h != nil {
   595  		return *h
   596  	}
   597  	_, hash := kbfshash.DoRawDefaultHash(fb.Contents)
   598  	fb.cacheMtx.Lock()
   599  	defer fb.cacheMtx.Unlock()
   600  	fb.hash = &hash
   601  	return *fb.hash
   602  }
   603  
   604  // OffsetExceedsData implements the Block interface for FileBlock.
   605  func (fb *FileBlock) OffsetExceedsData(startOff, off Offset) bool {
   606  	if fb.IsInd {
   607  		panic("OffsetExceedsData called on an indirect file block")
   608  	}
   609  
   610  	if len(fb.Contents) == 0 {
   611  		return false
   612  	}
   613  
   614  	offI, ok := off.(Int64Offset)
   615  	if !ok {
   616  		panic(fmt.Sprintf("Bad offset of type %T passed to FileBlock", off))
   617  	}
   618  	startOffI, ok := startOff.(Int64Offset)
   619  	if !ok {
   620  		panic(fmt.Sprintf("Bad offset of type %T passed to FileBlock",
   621  			startOff))
   622  	}
   623  	return int64(offI) >= int64(startOffI)+int64(len(fb.Contents))
   624  }
   625  
   626  // BytesCanBeDirtied implements the Block interface for FileBlock.
   627  func (fb *FileBlock) BytesCanBeDirtied() int64 {
   628  	return int64(len(fb.Contents))
   629  }
   630  
   631  // FirstOffset implements the Block interface for FileBlock.
   632  func (fb *FileBlock) FirstOffset() Offset {
   633  	return Int64Offset(0)
   634  }
   635  
   636  // NumIndirectPtrs implements the BlockWithPtrs interface for FileBlock.
   637  func (fb *FileBlock) NumIndirectPtrs() int {
   638  	if !fb.IsInd {
   639  		panic("NumIndirectPtrs called on a direct file block")
   640  	}
   641  	return len(fb.IPtrs)
   642  }
   643  
   644  // IndirectPtr implements the BlockWithPtrs interface for FileBlock.
   645  func (fb *FileBlock) IndirectPtr(i int) (BlockInfo, Offset) {
   646  	if !fb.IsInd {
   647  		panic("IndirectPtr called on a direct file block")
   648  	}
   649  	iptr := fb.IPtrs[i]
   650  	return iptr.BlockInfo, iptr.Off
   651  }
   652  
   653  // AppendNewIndirectPtr implements the BlockWithPtrs interface for FileBlock.
   654  func (fb *FileBlock) AppendNewIndirectPtr(ptr BlockPointer, off Offset) {
   655  	if !fb.IsInd {
   656  		panic("AppendNewIndirectPtr called on a direct file block")
   657  	}
   658  	iOff, ok := off.(Int64Offset)
   659  	if !ok {
   660  		panic(fmt.Sprintf("AppendNewIndirectPtr called on a file block "+
   661  			"with a %T offset", off))
   662  	}
   663  	fb.IPtrs = append(fb.IPtrs, IndirectFilePtr{
   664  		BlockInfo: BlockInfo{
   665  			BlockPointer: ptr,
   666  			EncodedSize:  0,
   667  		},
   668  		Off: iOff,
   669  	})
   670  }
   671  
   672  // ClearIndirectPtrSize implements the BlockWithPtrs interface for FileBlock.
   673  func (fb *FileBlock) ClearIndirectPtrSize(i int) {
   674  	if !fb.IsInd {
   675  		panic("ClearIndirectPtrSize called on a direct file block")
   676  	}
   677  	fb.IPtrs[i].EncodedSize = 0
   678  }
   679  
   680  // SetIndirectPtrType implements the BlockWithPtrs interface for FileBlock.
   681  func (fb *FileBlock) SetIndirectPtrType(i int, dt BlockDirectType) {
   682  	if !fb.IsInd {
   683  		panic("SetIndirectPtrType called on a direct file block")
   684  	}
   685  	fb.IPtrs[i].DirectType = dt
   686  }
   687  
   688  // SwapIndirectPtrs implements the BlockWithPtrs interface for FileBlock.
   689  func (fb *FileBlock) SwapIndirectPtrs(i int, other BlockWithPtrs, otherI int) {
   690  	otherFB, ok := other.(*FileBlock)
   691  	if !ok {
   692  		panic(fmt.Sprintf(
   693  			"SwapIndirectPtrs cannot swap between block types: %T", other))
   694  	}
   695  
   696  	fb.IPtrs[i], otherFB.IPtrs[otherI] = otherFB.IPtrs[otherI], fb.IPtrs[i]
   697  }
   698  
   699  // SetIndirectPtrOff implements the BlockWithPtrs interface for FileBlock.
   700  func (fb *FileBlock) SetIndirectPtrOff(i int, off Offset) {
   701  	if !fb.IsInd {
   702  		panic("SetIndirectPtrOff called on a direct file block")
   703  	}
   704  	iOff, ok := off.(Int64Offset)
   705  	if !ok {
   706  		panic(fmt.Sprintf("SetIndirectPtrOff called on a file block "+
   707  			"with a %T offset", off))
   708  	}
   709  	fb.IPtrs[i].Off = iOff
   710  }
   711  
   712  // SetIndirectPtrInfo implements the BlockWithPtrs interface for FileBlock.
   713  func (fb *FileBlock) SetIndirectPtrInfo(i int, info BlockInfo) {
   714  	if !fb.IsInd {
   715  		panic("SetIndirectPtrInfo called on a direct file block")
   716  	}
   717  	fb.IPtrs[i].BlockInfo = info
   718  }
   719  
   720  // DefaultNewBlockDataVersion returns the default data version for new blocks.
   721  func DefaultNewBlockDataVersion(holes bool) Ver {
   722  	if holes {
   723  		return ChildHolesVer
   724  	}
   725  	return FirstValidVer
   726  }