github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }