github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/data/block_tree.go (about) 1 // Copyright 2018 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 "context" 9 "fmt" 10 "github.com/gammazero/workerpool" 11 "sort" 12 "sync" 13 14 "github.com/keybase/client/go/kbfs/kbfsblock" 15 "github.com/keybase/client/go/kbfs/libkey" 16 "github.com/keybase/client/go/kbfs/tlf" 17 "github.com/keybase/client/go/libkb" 18 "github.com/keybase/client/go/logger" 19 "github.com/keybase/client/go/protocol/keybase1" 20 "golang.org/x/sync/errgroup" 21 ) 22 23 const ( 24 // maxBlockFetchWorkers specifies the number of parallel 25 // goroutines allowed when fetching blocks recursively in 26 // parallel. 27 maxBlockFetchWorkers = 100 28 29 // maxParallelReadies specifies the number of block ready calls to 30 // be made simultaneously for a given block tree. 31 maxParallelReadies = 10 32 ) 33 34 // blockGetterFn is a function that gets a block suitable for reading 35 // or writing, and also returns whether the block was already dirty. 36 // It may be called from new goroutines, and must handle any required 37 // locks accordingly. 38 type blockGetterFn func(context.Context, libkey.KeyMetadata, BlockPointer, 39 Path, BlockReqType) (block BlockWithPtrs, wasDirty bool, err error) 40 41 // dirtyBlockCacher writes dirty blocks to a cache. 42 type dirtyBlockCacher func( 43 ctx context.Context, ptr BlockPointer, block Block) error 44 45 type blockTree struct { 46 file Path 47 chargedTo keybase1.UserOrTeamID 48 kmd libkey.KeyMetadata 49 bsplit BlockSplitter 50 getter blockGetterFn 51 cacher dirtyBlockCacher 52 log logger.Logger 53 vlog *libkb.VDebugLog 54 } 55 56 // ParentBlockAndChildIndex is a node on a path down the tree to a 57 // particular leaf node. `pblock` is an indirect block corresponding 58 // to one of that leaf node's parents, and `childIndex` is an index 59 // into `pblock.IPtrs` to the next node along the path. 60 type ParentBlockAndChildIndex struct { 61 pblock BlockWithPtrs 62 childIndex int 63 } 64 65 func (pbci ParentBlockAndChildIndex) childIPtr() (BlockInfo, Offset) { 66 return pbci.pblock.IndirectPtr(pbci.childIndex) 67 } 68 69 func (pbci ParentBlockAndChildIndex) childBlockPtr() BlockPointer { 70 info, _ := pbci.pblock.IndirectPtr(pbci.childIndex) 71 return info.BlockPointer 72 } 73 74 func (pbci ParentBlockAndChildIndex) clearEncodedSize() { 75 pbci.pblock.ClearIndirectPtrSize(pbci.childIndex) 76 } 77 78 func (pbci ParentBlockAndChildIndex) setChildBlockInfo(info BlockInfo) { 79 pbci.pblock.SetIndirectPtrInfo(pbci.childIndex, info) 80 } 81 82 func (bt *blockTree) rootBlockPointer() BlockPointer { 83 return bt.file.TailPointer() 84 } 85 86 // getBlockAtOffset returns the leaf block containing the given 87 // `off`, along with the set of indirect blocks leading to that leaf 88 // (if any). 89 func (bt *blockTree) getBlockAtOffset(ctx context.Context, 90 topBlock BlockWithPtrs, off Offset, rtype BlockReqType) ( 91 ptr BlockPointer, parentBlocks []ParentBlockAndChildIndex, 92 block BlockWithPtrs, nextBlockStartOff, startOff Offset, 93 wasDirty bool, err error) { 94 // Find the block matching the offset, if it exists. 95 ptr = bt.rootBlockPointer() 96 block = topBlock 97 nextBlockStartOff = nil 98 startOff = topBlock.FirstOffset() 99 100 if !topBlock.IsIndirect() { 101 // If it's not an indirect block, we just need to figure out 102 // if it's dirty. 103 _, wasDirty, err = bt.getter(ctx, bt.kmd, ptr, bt.file, rtype) 104 if err != nil { 105 return ZeroPtr, nil, nil, nil, nil, false, err 106 } 107 return ptr, nil, block, nextBlockStartOff, startOff, wasDirty, nil 108 } 109 110 // Search until it's not an indirect block. 111 for block.IsIndirect() { 112 nextIndex := block.NumIndirectPtrs() - 1 113 for i := 0; i < block.NumIndirectPtrs(); i++ { 114 _, iptrOff := block.IndirectPtr(i) 115 if iptrOff.Equals(off) { 116 // Small optimization to avoid iterating past the correct ptr. 117 nextIndex = i 118 break 119 } else if off.Less(iptrOff) { 120 // Use the previous block. i can never be 0, because 121 // the first ptr always has an offset at the beginning 122 // of the range. 123 nextIndex = i - 1 124 break 125 } 126 } 127 var info BlockInfo 128 info, startOff = block.IndirectPtr(nextIndex) 129 parentBlocks = append(parentBlocks, 130 ParentBlockAndChildIndex{block, nextIndex}) 131 // There is more to read if we ever took a path through a 132 // ptr that wasn't the final ptr in its respective list. 133 if nextIndex != block.NumIndirectPtrs()-1 { 134 _, nextBlockStartOff = block.IndirectPtr(nextIndex + 1) 135 } 136 ptr = info.BlockPointer 137 block, wasDirty, err = bt.getter( 138 ctx, bt.kmd, info.BlockPointer, bt.file, rtype) 139 if err != nil { 140 return ZeroPtr, nil, nil, nil, nil, false, err 141 } 142 } 143 144 return ptr, parentBlocks, block, nextBlockStartOff, startOff, wasDirty, nil 145 } 146 147 // getNextDirtyBlockAtOffsetAtLevel does the same thing as 148 // `getNextDirtyBlockAtOffset` (see the comments on that function) 149 // on a subsection of the block tree (not necessarily starting from 150 // the top block). 151 func (bt *blockTree) getNextDirtyBlockAtOffsetAtLevel(ctx context.Context, 152 pblock BlockWithPtrs, off Offset, rtype BlockReqType, 153 dirtyBcache IsDirtyProvider, parentBlocks []ParentBlockAndChildIndex) ( 154 ptr BlockPointer, newParentBlocks []ParentBlockAndChildIndex, 155 block BlockWithPtrs, nextBlockStartOff, startOff Offset, err error) { 156 // Search along paths of dirty blocks until we find a dirty leaf 157 // block with an offset equal or greater than `off`. 158 checkedPrevBlock := false 159 for i := 0; i < pblock.NumIndirectPtrs(); i++ { 160 info, iptrOff := pblock.IndirectPtr(i) 161 iptrLess := iptrOff.Less(off) 162 if iptrLess && i != pblock.NumIndirectPtrs()-1 { 163 continue 164 } 165 166 // No need to check the previous block if we align exactly 167 // with `off`, or this is the right-most leaf block. 168 if iptrLess || iptrOff.Equals(off) { 169 checkedPrevBlock = true 170 } 171 172 // If we haven't checked the previous block yet, do so now 173 // since it contains `off`. 174 index := -1 175 nextBlockStartOff = nil 176 var prevPtr BlockPointer 177 if !checkedPrevBlock && i > 0 { 178 prevInfo, _ := pblock.IndirectPtr(i - 1) 179 prevPtr = prevInfo.BlockPointer 180 } 181 if prevPtr.IsValid() && dirtyBcache.IsDirty( 182 bt.file.Tlf, prevPtr, bt.file.Branch) { 183 // Since we checked the previous block, stay on this 184 // index for the next iteration. 185 i-- 186 index = i 187 } else if dirtyBcache.IsDirty( 188 bt.file.Tlf, info.BlockPointer, bt.file.Branch) { 189 // Now check the current block. 190 index = i 191 } 192 checkedPrevBlock = true 193 194 // Try the next child. 195 if index == -1 { 196 continue 197 } 198 199 indexInfo, indexOff := pblock.IndirectPtr(index) 200 ptr = indexInfo.BlockPointer 201 block, _, err = bt.getter(ctx, bt.kmd, ptr, bt.file, rtype) 202 if err != nil { 203 return ZeroPtr, nil, nil, nil, nil, err 204 } 205 206 newParentBlocks = make( 207 []ParentBlockAndChildIndex, len(parentBlocks), len(parentBlocks)+1) 208 copy(newParentBlocks, parentBlocks) 209 newParentBlocks = append(newParentBlocks, 210 ParentBlockAndChildIndex{pblock, index}) 211 // If this is a leaf block, we're done. 212 if !block.IsIndirect() { 213 // There is more to read if we ever took a path through a 214 // ptr that wasn't the final ptr in its respective list. 215 if index != pblock.NumIndirectPtrs()-1 { 216 _, nextBlockStartOff = pblock.IndirectPtr(index + 1) 217 } 218 return ptr, newParentBlocks, block, nextBlockStartOff, indexOff, nil 219 } 220 221 // Recurse to the next lower level. 222 ptr, newParentBlocks, block, nextBlockStartOff, startOff, err = 223 bt.getNextDirtyBlockAtOffsetAtLevel( 224 ctx, block, off, rtype, dirtyBcache, newParentBlocks) 225 if err != nil { 226 return ZeroPtr, nil, nil, nil, nil, err 227 } 228 // If we found a block, we're done. 229 if block != nil { 230 // If the block didn't have an immediate sibling to the 231 // right, set the next offset to the parent block's 232 // sibling's offset. 233 if nextBlockStartOff == nil && index != pblock.NumIndirectPtrs()-1 { 234 _, nextBlockStartOff = pblock.IndirectPtr(index + 1) 235 } 236 return ptr, newParentBlocks, block, nextBlockStartOff, startOff, nil 237 } 238 } 239 240 // There's no dirty block at or after `off`. 241 return ZeroPtr, nil, nil, pblock.FirstOffset(), pblock.FirstOffset(), nil 242 } 243 244 // getNextDirtyBlockAtOffset returns the next dirty leaf block with a 245 // starting offset that is equal or greater than the given `off`. 246 // This assumes that any code that dirties a leaf block also dirties 247 // all of its parents, even if those parents haven't yet changed. It 248 // can be used iteratively (by feeding `nextBlockStartOff` back in as 249 // `off`) to find all the dirty blocks. Note that there is no need to 250 // parallelize that process, since all the dirty blocks are guaranteed 251 // to be local. `nextBlockStartOff` is `nil` if there's no next block. 252 func (bt *blockTree) getNextDirtyBlockAtOffset(ctx context.Context, 253 topBlock BlockWithPtrs, off Offset, rtype BlockReqType, 254 dirtyBcache IsDirtyProvider) ( 255 ptr BlockPointer, parentBlocks []ParentBlockAndChildIndex, 256 block BlockWithPtrs, nextBlockStartOff, startOff Offset, err error) { 257 // Find the block matching the offset, if it exists. 258 ptr = bt.rootBlockPointer() 259 if !dirtyBcache.IsDirty(bt.file.Tlf, ptr, bt.file.Branch) { 260 // The top block isn't dirty, so we know none of the leaves 261 // are dirty. 262 return ZeroPtr, nil, nil, topBlock.FirstOffset(), 263 topBlock.FirstOffset(), nil 264 } else if !topBlock.IsIndirect() { 265 // A dirty, direct block. 266 return bt.rootBlockPointer(), nil, topBlock, nil, 267 topBlock.FirstOffset(), nil 268 } 269 270 ptr, parentBlocks, block, nextBlockStartOff, startOff, err = 271 bt.getNextDirtyBlockAtOffsetAtLevel( 272 ctx, topBlock, off, rtype, dirtyBcache, nil) 273 if err != nil { 274 return ZeroPtr, nil, nil, nil, nil, err 275 } 276 if block == nil { 277 return ZeroPtr, nil, nil, topBlock.FirstOffset(), 278 topBlock.FirstOffset(), nil 279 } 280 281 // The leaf block doesn't cover this index. (If the contents 282 // length is 0, then this is the start or end of a hole, and it 283 // should still count as dirty.) 284 if block.OffsetExceedsData(startOff, off) { 285 return ZeroPtr, nil, nil, nil, topBlock.FirstOffset(), nil 286 } 287 288 return ptr, parentBlocks, block, nextBlockStartOff, startOff, nil 289 } 290 291 // getBlocksForOffsetRangeTask is used for passing data to 292 // getBlocksForOffsetRange tasks. 293 type getBlocksForOffsetRangeTask struct { 294 ptr BlockPointer 295 pblock BlockWithPtrs 296 pathPrefix []ParentBlockAndChildIndex 297 startOff Offset 298 endOff Offset 299 prefixOk bool 300 getDirect bool 301 // firstBlock is true if this is the first block in the range being fetched. 302 firstBlock bool 303 } 304 305 func (task *getBlocksForOffsetRangeTask) subTask( 306 childPtr BlockPointer, childPath []ParentBlockAndChildIndex, 307 firstBlock bool) getBlocksForOffsetRangeTask { 308 subTask := *task 309 subTask.ptr = childPtr 310 subTask.pblock = nil 311 subTask.pathPrefix = childPath 312 subTask.firstBlock = firstBlock 313 return subTask 314 } 315 316 // getBlocksForOffsetRangeResult is used for passing data back from 317 // getBlocksForOffsetRange tasks. 318 type getBlocksForOffsetRangeResult struct { 319 pathFromRoot []ParentBlockAndChildIndex 320 ptr BlockPointer 321 block Block 322 nextBlockOffset Offset 323 firstBlock bool 324 err error 325 } 326 327 // processGetBlocksTask examines the block it is passed, enqueueing any children 328 // in range into wp, and passing data back through results. 329 func (bt *blockTree) processGetBlocksTask(ctx context.Context, 330 wg *sync.WaitGroup, wp *workerpool.WorkerPool, 331 job getBlocksForOffsetRangeTask, 332 results chan<- getBlocksForOffsetRangeResult) { 333 defer wg.Done() 334 335 select { 336 case <-ctx.Done(): 337 results <- getBlocksForOffsetRangeResult{err: ctx.Err()} 338 return 339 default: 340 } 341 342 // We may have been passed just a pointer and need to fetch the block here. 343 var pblock BlockWithPtrs 344 if job.pblock == nil { 345 var err error 346 pblock, _, err = bt.getter(ctx, bt.kmd, job.ptr, bt.file, BlockReadParallel) 347 if err != nil { 348 results <- getBlocksForOffsetRangeResult{ 349 firstBlock: job.firstBlock, 350 err: err, 351 } 352 return 353 } 354 } else { 355 pblock = job.pblock 356 } 357 358 if !pblock.IsIndirect() { 359 // Return this block, under the assumption that the 360 // caller already checked the range for this block. 361 if job.getDirect { 362 results <- getBlocksForOffsetRangeResult{ 363 pathFromRoot: job.pathPrefix, 364 ptr: job.ptr, 365 block: pblock, 366 nextBlockOffset: nil, 367 firstBlock: job.firstBlock, 368 err: nil, 369 } 370 } 371 return 372 } 373 374 // Search all of the in-range child blocks, and their child 375 // blocks, etc, in parallel. 376 childIsFirstBlock := job.firstBlock 377 for i := 0; i < pblock.NumIndirectPtrs(); i++ { 378 info, iptrOff := pblock.IndirectPtr(i) 379 // Some byte of this block is included in the left side of the 380 // range if `job.startOff` is less than the largest byte offset in 381 // the block. 382 inRangeLeft := true 383 if i < pblock.NumIndirectPtrs()-1 { 384 _, off := pblock.IndirectPtr(i + 1) 385 inRangeLeft = job.startOff.Less(off) 386 } 387 if !inRangeLeft { 388 continue 389 } 390 // Some byte of this block is included in the right side of 391 // the range if `job.endOff` is bigger than the smallest byte 392 // offset in the block (or if we're explicitly reading all the 393 // data to the end). 394 inRangeRight := job.endOff == nil || iptrOff.Less(job.endOff) 395 if !inRangeRight { 396 // This block is the first one past the offset range 397 // amount the children. 398 results <- getBlocksForOffsetRangeResult{nextBlockOffset: iptrOff} 399 return 400 } 401 402 childPtr := info.BlockPointer 403 childIndex := i 404 405 childPath := make([]ParentBlockAndChildIndex, len(job.pathPrefix)+1) 406 copy(childPath, job.pathPrefix) 407 childPath[len(childPath)-1] = ParentBlockAndChildIndex{ 408 pblock: pblock, 409 childIndex: childIndex, 410 } 411 412 // We only need to fetch direct blocks if we've been asked 413 // to do so. If the direct type of the pointer is 414 // unknown, we can assume all the children are direct 415 // blocks, since there weren't multiple levels of 416 // indirection before the introduction of the flag. 417 if job.getDirect || childPtr.DirectType == IndirectBlock { 418 subTask := job.subTask(childPtr, childPath, childIsFirstBlock) 419 420 // Enqueue the subTask with the WorkerPool. 421 wg.Add(1) 422 wp.Submit(func() { 423 bt.processGetBlocksTask(ctx, wg, wp, subTask, results) 424 }) 425 } else { 426 results <- getBlocksForOffsetRangeResult{ 427 pathFromRoot: childPath, 428 firstBlock: childIsFirstBlock, 429 } 430 } 431 childIsFirstBlock = false 432 } 433 } 434 435 func checkForHolesAndTruncate( 436 pathsFromRoot [][]ParentBlockAndChildIndex) [][]ParentBlockAndChildIndex { 437 var prevPath []ParentBlockAndChildIndex 438 for pathIdx, Path := range pathsFromRoot { 439 // Each path after the first must immediately follow the preceding path. 440 if pathIdx == 0 { 441 prevPath = Path 442 continue 443 } 444 // Find the first place the 2 paths differ. 445 // Verify that path is immediately after prevPath. 446 if len(Path) != len(prevPath) { 447 return pathsFromRoot[:pathIdx] 448 } 449 450 foundIncrement := false 451 for idx := range Path { 452 prevChild := prevPath[idx].childIndex 453 thisChild := Path[idx].childIndex 454 if foundIncrement { 455 if thisChild != 0 || prevChild != prevPath[idx-1]. 456 pblock.NumIndirectPtrs()-1 { 457 return pathsFromRoot[:pathIdx] 458 } 459 } else { 460 if prevChild+1 == thisChild { 461 foundIncrement = true 462 } else if prevChild != thisChild { 463 return pathsFromRoot[:pathIdx] 464 } 465 } 466 } 467 // If we never found where the two paths differ, 468 // then something has gone wrong. 469 if !foundIncrement { 470 return pathsFromRoot[:pathIdx] 471 } 472 prevPath = Path 473 } 474 return pathsFromRoot 475 } 476 477 // getBlocksForOffsetRange fetches all the blocks making up paths down 478 // the block tree to leaf ("direct") blocks that encompass the given 479 // offset range (half-inclusive) in the data. If `endOff` is nil, it 480 // returns blocks until reaching the end of the data. If `prefixOk` 481 // is true, the function will ignore context deadline errors and 482 // return whatever prefix of the data it could fetch within the 483 // deadine. Return params: 484 // 485 // - pathsFromRoot is a slice, ordered by offset, of paths from 486 // the root to each block that makes up the range. If the path is 487 // empty, it indicates that pblock is a direct block and has no 488 // children. 489 // - blocks: a map from block pointer to a data-containing leaf node 490 // in the given range of offsets, if `getDirect` is true. 491 // - nextBlockOff is the offset of the block that follows the last 492 // block given in `pathsFromRoot`. If `pathsFromRoot` contains 493 // the last block among the children, nextBlockOff is nil. 494 func (bt *blockTree) getBlocksForOffsetRange(ctx context.Context, 495 ptr BlockPointer, pblock BlockWithPtrs, startOff, endOff Offset, 496 prefixOk bool, getDirect bool) (pathsFromRoot [][]ParentBlockAndChildIndex, 497 blocks map[BlockPointer]Block, nextBlockOffset Offset, 498 err error) { 499 // Make a WaitGroup to keep track of whether there's still work to be done. 500 var wg sync.WaitGroup 501 502 // Make a workerpool to limit the number of concurrent goroutines. 503 wp := workerpool.New(maxBlockFetchWorkers) 504 505 // Make a context to cancel all the jobs if something goes wrong. 506 groupCtx, cancel := context.WithCancel(ctx) 507 defer cancel() 508 509 // Make a queue for results coming back from workers. 510 results := make(chan getBlocksForOffsetRangeResult) 511 512 // Enqueue the top-level task. Increment the task counter by one. 513 rootTask := getBlocksForOffsetRangeTask{ 514 ptr: ptr, 515 pblock: pblock, 516 pathPrefix: nil, 517 startOff: startOff, 518 endOff: endOff, 519 prefixOk: prefixOk, 520 getDirect: getDirect, 521 firstBlock: true, 522 } 523 wg.Add(1) 524 wp.Submit(func() { 525 bt.processGetBlocksTask(groupCtx, &wg, wp, rootTask, results) 526 }) 527 528 // Once all the work is done, stop the WorkerPool and close `results` so 529 // that the loop below exits 530 go func() { 531 wg.Wait() 532 wp.Stop() 533 close(results) 534 }() 535 536 // Reduce all the results coming in over the results channel. 537 var minNextBlockOffset Offset 538 blocks = make(map[BlockPointer]Block) 539 pathsFromRoot = [][]ParentBlockAndChildIndex{} 540 mustCheckForHoles := false 541 gotFirstBlock := false 542 var errors []error 543 for res := range results { 544 if res.err != nil { 545 // If we are ok with just getting the prefix, don't treat a 546 // deadline exceeded error as fatal. 547 if prefixOk && res.err == context.DeadlineExceeded && 548 !res.firstBlock && len(errors) == 0 { 549 mustCheckForHoles = true 550 } else { 551 errors = append(errors, res.err) 552 } 553 cancel() 554 } 555 if res.pathFromRoot != nil { 556 pathsFromRoot = append(pathsFromRoot, res.pathFromRoot) 557 } 558 if res.block != nil { 559 blocks[res.ptr] = res.block 560 } 561 if res.nextBlockOffset != nil && 562 (minNextBlockOffset == nil || 563 res.nextBlockOffset.Less(minNextBlockOffset)) { 564 minNextBlockOffset = res.nextBlockOffset 565 } 566 if res.firstBlock { 567 gotFirstBlock = true 568 } 569 } 570 nextBlockOffset = minNextBlockOffset 571 572 if len(errors) == 1 { 573 return nil, nil, nil, errors[0] 574 } else if len(errors) > 1 { 575 return nil, nil, nil, fmt.Errorf("multiple errors: %v", errors) 576 } 577 578 // Out-of-order traversal means the paths come back from workers unsorted. 579 // Sort them before returning them to the caller. 580 sort.Slice(pathsFromRoot, func(i, j int) bool { 581 pathI := pathsFromRoot[i] 582 pathJ := pathsFromRoot[j] 583 lastChildI := pathI[len(pathI)-1] 584 lastChildJ := pathJ[len(pathJ)-1] 585 586 _, offsetI := lastChildI.pblock.IndirectPtr(lastChildI.childIndex) 587 _, offsetJ := lastChildJ.pblock.IndirectPtr(lastChildJ.childIndex) 588 589 return offsetI.Less(offsetJ) 590 }) 591 592 // If we are returning data even though not all the goroutines completed, 593 // we may need to return only some of the data we gathered in order to 594 // return a correct prefix of the data. Thus, we find the longest prefix of 595 // the data without any holes. 596 if !gotFirstBlock { 597 pathsFromRoot = [][]ParentBlockAndChildIndex{} 598 } else if mustCheckForHoles { 599 pathsFromRoot = checkForHolesAndTruncate(pathsFromRoot) 600 } 601 602 return pathsFromRoot, blocks, nextBlockOffset, nil 603 } 604 605 type createTopBlockFn func(context.Context, Ver) (BlockWithPtrs, error) 606 type makeNewBlockWithPtrs func(isIndirect bool) BlockWithPtrs 607 608 // newRightBlock creates space for a new rightmost block, creating 609 // parent blocks and a new level of indirection in the tree as needed. 610 // If there's no new level of indirection, it modifies the blocks in 611 // `parentBlocks` to include the new right-most pointers 612 // (`parentBlocks` must consist of blocks copied for writing). It 613 // also returns the set of parents pointing to the new block (whether 614 // or not there is a new level of indirection), and also returns any 615 // newly-dirtied block pointers. 616 // 617 // The new block is pointed to using offset `off`, and doesn't have to 618 // represent the right-most block in a tree. In particular, if `off` 619 // is less than the offset of its leftmost neighbor, it's the caller's 620 // responsibility to move the new right block into the correct place 621 // in the tree (e.g., using `shiftBlocksToFillHole()`). 622 func (bt *blockTree) newRightBlock( 623 ctx context.Context, parentBlocks []ParentBlockAndChildIndex, off Offset, 624 dver Ver, newBlock makeNewBlockWithPtrs, topBlocker createTopBlockFn) ( 625 []ParentBlockAndChildIndex, []BlockPointer, error) { 626 // Find the lowest block that can accommodate a new right block. 627 lowestAncestorWithRoom := -1 628 for i := len(parentBlocks) - 1; i >= 0; i-- { 629 pb := parentBlocks[i] 630 if pb.pblock.NumIndirectPtrs() < bt.bsplit.MaxPtrsPerBlock() { 631 lowestAncestorWithRoom = i 632 break 633 } 634 } 635 636 var newTopBlock BlockWithPtrs 637 var newDirtyPtrs []BlockPointer 638 if lowestAncestorWithRoom < 0 { 639 // Create a new level of indirection at the top. 640 var err error 641 newTopBlock, err = topBlocker(ctx, dver) 642 if err != nil { 643 return nil, nil, err 644 } 645 646 // The old top block needs to be cached under its new ID if it 647 // was indirect. 648 if len(parentBlocks) > 0 { 649 dType := DirectBlock 650 if parentBlocks[0].pblock.IsIndirect() { 651 dType = IndirectBlock 652 } 653 newTopBlock.SetIndirectPtrType(0, dType) 654 info, _ := newTopBlock.IndirectPtr(0) 655 ptr := info.BlockPointer 656 err = bt.cacher(ctx, ptr, parentBlocks[0].pblock) 657 if err != nil { 658 return nil, nil, err 659 } 660 newDirtyPtrs = append(newDirtyPtrs, ptr) 661 } 662 663 parentBlocks = append([]ParentBlockAndChildIndex{{newTopBlock, 0}}, 664 parentBlocks...) 665 lowestAncestorWithRoom = 0 666 } 667 rightParentBlocks := make([]ParentBlockAndChildIndex, len(parentBlocks)) 668 669 bt.vlog.CLogf( 670 ctx, libkb.VLog1, "Making new right block at off %s for entry %v, "+ 671 "lowestAncestor at level %d", off, bt.rootBlockPointer(), 672 lowestAncestorWithRoom) 673 674 // Make a new right block for every parent, starting with the 675 // lowest ancestor with room. Note that we're not iterating over 676 // the actual parent blocks here; we're only using its length to 677 // figure out how many levels need new blocks. 678 pblock := parentBlocks[lowestAncestorWithRoom].pblock 679 parentPtr := bt.rootBlockPointer() 680 if lowestAncestorWithRoom > 0 { 681 parentPtr = parentBlocks[lowestAncestorWithRoom-1].childBlockPtr() 682 } 683 for i := lowestAncestorWithRoom; i < len(parentBlocks); i++ { 684 newRID, err := kbfsblock.MakeTemporaryID() 685 if err != nil { 686 return nil, nil, err 687 } 688 689 newPtr := BlockPointer{ 690 ID: newRID, 691 KeyGen: bt.kmd.LatestKeyGeneration(), 692 DataVer: dver, 693 Context: kbfsblock.MakeFirstContext( 694 bt.chargedTo, bt.rootBlockPointer().GetBlockType()), 695 DirectType: IndirectBlock, 696 } 697 698 if i == len(parentBlocks)-1 { 699 newPtr.DirectType = DirectBlock 700 } 701 702 bt.vlog.CLogf( 703 ctx, libkb.VLog1, "New right block for entry %v, level %d, ptr %v", 704 bt.rootBlockPointer(), i, newPtr) 705 706 pblock.AppendNewIndirectPtr(newPtr, off) 707 rightParentBlocks[i].pblock = pblock 708 rightParentBlocks[i].childIndex = pblock.NumIndirectPtrs() - 1 709 err = bt.cacher(ctx, parentPtr, pblock) 710 if err != nil { 711 return nil, nil, err 712 } 713 714 isInd := i != len(parentBlocks)-1 715 rblock := newBlock(isInd) 716 if isInd { 717 pblock = rblock 718 parentPtr = newPtr 719 } 720 721 err = bt.cacher(ctx, newPtr, rblock) 722 if err != nil { 723 return nil, nil, err 724 } 725 726 newDirtyPtrs = append(newDirtyPtrs, newPtr) 727 } 728 729 // All parents up to and including the lowest ancestor with room 730 // will have to change, so mark them as dirty. 731 ptr := bt.rootBlockPointer() 732 for i := 0; i <= lowestAncestorWithRoom; i++ { 733 pb := parentBlocks[i] 734 if err := bt.cacher(ctx, ptr, pb.pblock); err != nil { 735 return nil, nil, err 736 } 737 newDirtyPtrs = append(newDirtyPtrs, ptr) 738 ptr = pb.childBlockPtr() 739 rightParentBlocks[i].pblock = pb.pblock 740 rightParentBlocks[i].childIndex = pb.pblock.NumIndirectPtrs() - 1 741 } 742 743 return rightParentBlocks, newDirtyPtrs, nil 744 } 745 746 // setParentOffsets updates the parent offsets for a newly-moved 747 // block, all the way up to its common ancestor (which is the one that 748 // doesn't have a childIndex of 0). 749 func (bt *blockTree) setParentOffsets( 750 ctx context.Context, newOff Offset, 751 parents []ParentBlockAndChildIndex, currIndex int) ( 752 newDirtyPtrs []BlockPointer, newUnrefs []BlockInfo, err error) { 753 for level := len(parents) - 2; level >= 0; level-- { 754 // Cache the block below this level, which was just 755 // modified. 756 childInfo, _ := parents[level].childIPtr() 757 if err := bt.cacher( 758 ctx, childInfo.BlockPointer, parents[level+1].pblock); err != nil { 759 return nil, nil, err 760 } 761 newDirtyPtrs = append(newDirtyPtrs, childInfo.BlockPointer) 762 // Remember the size of the dirtied child. 763 if childInfo.EncodedSize != 0 { 764 newUnrefs = append(newUnrefs, childInfo) 765 parents[level].clearEncodedSize() 766 } 767 768 // If we've reached a level where the child indirect 769 // offset wasn't affected, we're done. If not, update the 770 // offset at this level and move up the tree. 771 if currIndex > 0 { 772 break 773 } 774 currIndex = parents[level].childIndex 775 parents[level].pblock.SetIndirectPtrOff(currIndex, newOff) 776 } 777 return newDirtyPtrs, newUnrefs, nil 778 } 779 780 func (bt *blockTree) String() string { 781 block, _, err := bt.getter( 782 nil, bt.kmd, bt.rootBlockPointer(), bt.file, BlockRead) 783 if err != nil { 784 return "ERROR: " + err.Error() 785 } 786 787 level := []BlockWithPtrs{block} 788 // TODO: use a `bytes.Buffer` instead of a regular string here if 789 // we ever use this function from real code. 790 res := "\n---------------\n" 791 for len(level) > 0 { 792 var nextLevel []BlockWithPtrs 793 for i, block := range level { 794 if !block.IsIndirect() { 795 continue 796 } 797 for j := 0; j < block.NumIndirectPtrs(); j++ { 798 info, off := block.IndirectPtr(j) 799 res += fmt.Sprintf("\"%s\" ", off) 800 if info.DirectType == DirectBlock { 801 continue 802 } 803 child, _, err := bt.getter( 804 nil, bt.kmd, info.BlockPointer, bt.file, BlockRead) 805 if err != nil { 806 return "ERROR: " + err.Error() 807 } 808 nextLevel = append(nextLevel, child) 809 } 810 if i+1 < len(level) { 811 res += "| " 812 } 813 } 814 res += "\n" 815 level = nextLevel 816 } 817 res += "---------------\n" 818 return res 819 } 820 821 // shiftBlocksToFillHole should be called after newRightBlock when the 822 // offset for the new block is smaller than the final offset of the 823 // tree. This happens when there is a hole in the file, or when 824 // expanding an internal leaf for a directory, and the user is now 825 // writing data into that expanded area. This function moves the new 826 // block into the correct place, and rearranges all the indirect 827 // pointers in the file as needed. It returns any block pointers that 828 // were dirtied in the process. 829 func (bt *blockTree) shiftBlocksToFillHole( 830 ctx context.Context, parents []ParentBlockAndChildIndex) ( 831 newDirtyPtrs []BlockPointer, newUnrefs []BlockInfo, 832 newlyDirtiedChildBytes int64, err error) { 833 // `parents` should represent the right side of the tree down to 834 // the new rightmost indirect pointer, the offset of which should 835 // match `newHoleStartOff`. Keep swapping it with its sibling on 836 // the left until its offset would be lower than that child's 837 // offset. If there are no children to the left, continue on with 838 // the children in the cousin block to the left. If we swap a 839 // child between cousin blocks, we must update the offset in the 840 // right cousin's parent block. If *that* updated pointer is the 841 // leftmost pointer in its parent block, update that one as well, 842 // up to the root. 843 // 844 // We are guaranteed at least one level of indirection because 845 // `newRightBlock` should have been called before 846 // `shiftBlocksToFillHole`. 847 immedParent := parents[len(parents)-1] 848 currIndex := immedParent.childIndex 849 _, newBlockStartOff := immedParent.childIPtr() 850 851 bt.vlog.CLogf( 852 ctx, libkb.VLog1, "Shifting block with offset %s for entry %v into "+ 853 "position", newBlockStartOff, bt.rootBlockPointer()) 854 855 // Swap left as needed. 856 for loopedOnce := false; ; loopedOnce = true { 857 var leftOff Offset 858 var newParents []ParentBlockAndChildIndex 859 immedPblock := immedParent.pblock 860 if currIndex > 0 { 861 _, leftOff = immedPblock.IndirectPtr(currIndex - 1) 862 } else { 863 if loopedOnce { 864 // Now update the left side if needed, before looking into 865 // swapping across blocks. 866 bt.vlog.CLogf(ctx, libkb.VLog1, "Updating on left side") 867 _, newOff := immedPblock.IndirectPtr(currIndex) 868 ndp, nu, err := bt.setParentOffsets( 869 ctx, newOff, parents, currIndex) 870 if err != nil { 871 return nil, nil, 0, err 872 } 873 newDirtyPtrs = append(newDirtyPtrs, ndp...) 874 newUnrefs = append(newUnrefs, nu...) 875 } 876 877 // Construct the new set of parents for the shifted block, 878 // by looking for the next left cousin. 879 newParents = make([]ParentBlockAndChildIndex, len(parents)) 880 copy(newParents, parents) 881 var level int 882 for level = len(newParents) - 2; level >= 0; level-- { 883 // The parent at the level being evaluated has a left 884 // sibling, so we use that sibling. 885 if newParents[level].childIndex > 0 { 886 break 887 } 888 // Keep going up until we find a way back down a left branch. 889 } 890 891 if level < 0 { 892 // We are already all the way on the left, we're done! 893 return newDirtyPtrs, newUnrefs, newlyDirtiedChildBytes, nil 894 } 895 newParents[level].childIndex-- 896 897 // Walk back down, shifting the new parents into position. 898 for ; level < len(newParents)-1; level++ { 899 nextPtr := newParents[level].childBlockPtr() 900 childBlock, _, err := bt.getter( 901 ctx, bt.kmd, nextPtr, bt.file, BlockWrite) 902 if err != nil { 903 return nil, nil, 0, err 904 } 905 906 newParents[level+1].pblock = childBlock 907 newParents[level+1].childIndex = 908 childBlock.NumIndirectPtrs() - 1 909 _, leftOff = childBlock.IndirectPtr( 910 childBlock.NumIndirectPtrs() - 1) 911 } 912 } 913 914 // We're done! 915 if leftOff.Less(newBlockStartOff) { 916 return newDirtyPtrs, newUnrefs, newlyDirtiedChildBytes, nil 917 } 918 919 // Otherwise, we need to swap the indirect file pointers. 920 if currIndex > 0 { 921 immedPblock.SwapIndirectPtrs(currIndex-1, immedPblock, currIndex) 922 currIndex-- 923 continue 924 } 925 926 // Swap block pointers across cousins at the lowest level of 927 // indirection. 928 newImmedParent := newParents[len(newParents)-1] 929 newImmedPblock := newImmedParent.pblock 930 newCurrIndex := newImmedPblock.NumIndirectPtrs() - 1 931 newImmedPblock.SwapIndirectPtrs(newCurrIndex, immedPblock, currIndex) 932 933 // Cache the new immediate parent as dirty. Also cache the 934 // old immediate parent's right-most leaf child as dirty, to 935 // make sure this path is captured in 936 // getNextDirtyBlockAtOffset calls. TODO: this is inefficient 937 // since it might end up re-encoding and re-uploading a leaf 938 // block that wasn't actually dirty; we should find a better 939 // way to make sure ready() sees these parent blocks. 940 if len(newParents) > 1 { 941 i := len(newParents) - 2 942 childPtr := newParents[i].childBlockPtr() 943 if err := bt.cacher( 944 ctx, childPtr, newImmedPblock); err != nil { 945 return nil, nil, 0, err 946 } 947 newDirtyPtrs = append(newDirtyPtrs, childPtr) 948 949 // Fetch the old parent's right leaf for writing, and mark 950 // it as dirty. 951 rightLeafInfo, _ := immedPblock.IndirectPtr( 952 immedPblock.NumIndirectPtrs() - 1) 953 leafBlock, _, err := bt.getter( 954 ctx, bt.kmd, rightLeafInfo.BlockPointer, bt.file, BlockWrite) 955 if err != nil { 956 return nil, nil, 0, err 957 } 958 if err := bt.cacher( 959 ctx, rightLeafInfo.BlockPointer, leafBlock); err != nil { 960 return nil, nil, 0, err 961 } 962 newDirtyPtrs = append(newDirtyPtrs, rightLeafInfo.BlockPointer) 963 // Remember the size of the dirtied leaf. 964 if rightLeafInfo.EncodedSize != 0 { 965 newlyDirtiedChildBytes += leafBlock.BytesCanBeDirtied() 966 newUnrefs = append(newUnrefs, rightLeafInfo) 967 immedPblock.ClearIndirectPtrSize( 968 immedPblock.NumIndirectPtrs() - 1) 969 } 970 } 971 972 // Now we need to update the parent offsets on the right side, 973 // all the way up to the common ancestor (which is the one 974 // with the one that doesn't have a childIndex of 0). 975 _, newRightOff := immedPblock.IndirectPtr(currIndex) 976 ndp, nu, err := bt.setParentOffsets( 977 ctx, newRightOff, parents, currIndex) 978 if err != nil { 979 return nil, nil, 0, err 980 } 981 newDirtyPtrs = append(newDirtyPtrs, ndp...) 982 newUnrefs = append(newUnrefs, nu...) 983 984 immedParent = newImmedParent 985 currIndex = newCurrIndex 986 parents = newParents 987 } 988 // The loop above must exit via one of the returns. 989 } 990 991 // markParentsDirty caches all the blocks in `parentBlocks` as dirty, 992 // and returns the dirtied block pointers as well as any block infos 993 // with non-zero encoded sizes that will now need to be unreferenced. 994 func (bt *blockTree) markParentsDirty( 995 ctx context.Context, parentBlocks []ParentBlockAndChildIndex) ( 996 dirtyPtrs []BlockPointer, unrefs []BlockInfo, err error) { 997 parentPtr := bt.rootBlockPointer() 998 for _, pb := range parentBlocks { 999 dirtyPtrs = append(dirtyPtrs, parentPtr) 1000 childInfo, _ := pb.childIPtr() 1001 1002 // Remember the size of each newly-dirtied child. 1003 if childInfo.EncodedSize != 0 { 1004 unrefs = append(unrefs, childInfo) 1005 pb.clearEncodedSize() 1006 } 1007 if err := bt.cacher(ctx, parentPtr, pb.pblock); err != nil { 1008 return nil, unrefs, err 1009 } 1010 parentPtr = childInfo.BlockPointer 1011 } 1012 return dirtyPtrs, unrefs, nil 1013 } 1014 1015 type makeSyncFunc func(ptr BlockPointer) func() error 1016 1017 func (bt *blockTree) readyWorker( 1018 ctx context.Context, id tlf.ID, bcache BlockCache, rp ReadyProvider, 1019 bps BlockPutState, pathsFromRoot [][]ParentBlockAndChildIndex, 1020 makeSync makeSyncFunc, i int, level int, lock *sync.Mutex, 1021 oldPtrs map[BlockInfo]BlockPointer, donePtrs map[BlockPointer]bool, 1022 hashBehavior BlockCacheHashBehavior) error { 1023 // Ready the dirty block. 1024 pb := pathsFromRoot[i][level] 1025 1026 lock.Lock() 1027 parentPB := pathsFromRoot[i][level-1] 1028 ptr := parentPB.childBlockPtr() 1029 // If this is already a new pointer, skip it. 1030 if donePtrs[ptr] { 1031 lock.Unlock() 1032 return nil 1033 } 1034 donePtrs[ptr] = true 1035 lock.Unlock() 1036 1037 newInfo, _, readyBlockData, err := ReadyBlock( 1038 ctx, bcache, rp, bt.kmd, pb.pblock, 1039 bt.chargedTo, bt.rootBlockPointer().GetBlockType(), hashBehavior) 1040 if err != nil { 1041 return err 1042 } 1043 1044 lock.Lock() 1045 defer lock.Unlock() 1046 1047 err = bcache.Put( 1048 newInfo.BlockPointer, id, pb.pblock, PermanentEntry, SkipCacheHash) 1049 if err != nil { 1050 return err 1051 } 1052 1053 // Only the leaf level need to be tracked by the dirty file. 1054 var syncFunc func() error 1055 if makeSync != nil && level == len(pathsFromRoot[0])-1 { 1056 syncFunc = makeSync(ptr) 1057 } 1058 1059 err = bps.AddNewBlock( 1060 ctx, newInfo.BlockPointer, pb.pblock, readyBlockData, 1061 syncFunc) 1062 if err != nil { 1063 return err 1064 } 1065 err = bps.SaveOldPtr(ctx, ptr) 1066 if err != nil { 1067 return err 1068 } 1069 1070 parentPB.setChildBlockInfo(newInfo) 1071 oldPtrs[newInfo] = ptr 1072 donePtrs[newInfo.BlockPointer] = true 1073 return nil 1074 } 1075 1076 // readyHelper takes a set of paths from a root down to a child block, 1077 // and readies all the blocks represented in those paths. If the 1078 // caller wants leaf blocks readied, then the last element of each 1079 // slice in `pathsFromRoot` should contain a leaf block, with a child 1080 // index of -1. It's assumed that all slices in `pathsFromRoot` have 1081 // the same size. This function returns a map pointing from the new 1082 // block info from any readied block to its corresponding old block 1083 // pointer. 1084 func (bt *blockTree) readyHelper( 1085 ctx context.Context, id tlf.ID, bcache BlockCache, 1086 rp ReadyProvider, bps BlockPutState, 1087 pathsFromRoot [][]ParentBlockAndChildIndex, makeSync makeSyncFunc, 1088 hashBehavior BlockCacheHashBehavior) ( 1089 map[BlockInfo]BlockPointer, error) { 1090 oldPtrs := make(map[BlockInfo]BlockPointer) 1091 donePtrs := make(map[BlockPointer]bool) 1092 1093 // lock protects `bps`, `oldPtrs`, and `donePtrs` while 1094 // parallelizing block readies below. 1095 var lock sync.Mutex 1096 1097 // Starting from the leaf level, ready each block at each level, 1098 // and put the new BlockInfo into the parent block at the level 1099 // above. At each level, only ready each block once. Don't ready 1100 // the root block though; the folderUpdatePrepper code will do 1101 // that. 1102 for level := len(pathsFromRoot[0]) - 1; level > 0; level-- { 1103 eg, groupCtx := errgroup.WithContext(ctx) 1104 indices := make(chan int, len(pathsFromRoot)) 1105 numWorkers := len(pathsFromRoot) 1106 if numWorkers > maxParallelReadies { 1107 numWorkers = maxParallelReadies 1108 } 1109 1110 worker := func() error { 1111 for i := range indices { 1112 err := bt.readyWorker( 1113 groupCtx, id, bcache, rp, bps, pathsFromRoot, makeSync, 1114 i, level, &lock, oldPtrs, donePtrs, hashBehavior) 1115 if err != nil { 1116 return err 1117 } 1118 } 1119 return nil 1120 } 1121 for i := 0; i < numWorkers; i++ { 1122 eg.Go(worker) 1123 } 1124 1125 for i := 0; i < len(pathsFromRoot); i++ { 1126 indices <- i 1127 } 1128 close(indices) 1129 err := eg.Wait() 1130 if err != nil { 1131 return nil, err 1132 } 1133 } 1134 return oldPtrs, nil 1135 } 1136 1137 // ready, if given an indirect top-block, readies all the dirty child 1138 // blocks, and updates their block IDs in their parent block's list of 1139 // indirect pointers. It returns a map pointing from the new block 1140 // info from any readied block to its corresponding old block pointer. 1141 func (bt *blockTree) ready( 1142 ctx context.Context, id tlf.ID, bcache BlockCache, 1143 dirtyBcache IsDirtyProvider, rp ReadyProvider, bps BlockPutState, 1144 topBlock BlockWithPtrs, makeSync makeSyncFunc, 1145 hashBehavior BlockCacheHashBehavior) ( 1146 map[BlockInfo]BlockPointer, error) { 1147 if !topBlock.IsIndirect() { 1148 return nil, nil 1149 } 1150 1151 // This will contain paths to all dirty leaf paths. The final 1152 // entry index in each path will be the leaf node block itself 1153 // (with a -1 child index). 1154 var dirtyLeafPaths [][]ParentBlockAndChildIndex 1155 1156 // Gather all the paths to all dirty leaf blocks first. 1157 off := topBlock.FirstOffset() 1158 for off != nil { 1159 _, parentBlocks, block, nextBlockOff, _, err := 1160 bt.getNextDirtyBlockAtOffset( 1161 ctx, topBlock, off, BlockWrite, dirtyBcache) 1162 if err != nil { 1163 return nil, err 1164 } 1165 1166 if block == nil { 1167 // No more dirty blocks. 1168 break 1169 } 1170 off = nextBlockOff // Will be `nil` if there are no more blocks. 1171 1172 // Make sure there's only one copy of each pblock among all 1173 // the paths, so `readyHelper` can update the blocks in place 1174 // along any path, and they will all be updated. 1175 for _, p := range dirtyLeafPaths { 1176 for i := range parentBlocks { 1177 if i == 0 || p[i-1].childBlockPtr() == 1178 parentBlocks[i-1].childBlockPtr() { 1179 parentBlocks[i].pblock = p[i].pblock 1180 } 1181 } 1182 } 1183 1184 dirtyLeafPaths = append(dirtyLeafPaths, 1185 append(parentBlocks, ParentBlockAndChildIndex{block, -1})) 1186 } 1187 1188 // No dirty blocks means nothing to do. 1189 if len(dirtyLeafPaths) == 0 { 1190 return nil, nil 1191 } 1192 1193 return bt.readyHelper( 1194 ctx, id, bcache, rp, bps, dirtyLeafPaths, makeSync, hashBehavior) 1195 } 1196 1197 func (bt *blockTree) getIndirectBlocksForOffsetRange( 1198 ctx context.Context, pblock BlockWithPtrs, startOff, endOff Offset) ( 1199 pathsFromRoot [][]ParentBlockAndChildIndex, err error) { 1200 // Fetch the paths of indirect blocks, without getting the direct 1201 // blocks. 1202 pfr, _, _, err := bt.getBlocksForOffsetRange( 1203 ctx, bt.rootBlockPointer(), pblock, startOff, endOff, false, 1204 false /* no direct blocks */) 1205 if err != nil { 1206 return nil, err 1207 } 1208 1209 return pfr, nil 1210 } 1211 1212 func (bt *blockTree) getIndirectBlockInfosWithTopBlock( 1213 ctx context.Context, topBlock BlockWithPtrs) ([]BlockInfo, error) { 1214 if !topBlock.IsIndirect() { 1215 return nil, nil 1216 } 1217 1218 pfr, err := bt.getIndirectBlocksForOffsetRange( 1219 ctx, topBlock, topBlock.FirstOffset(), nil) 1220 if err != nil { 1221 return nil, err 1222 } 1223 1224 var blockInfos []BlockInfo 1225 infoSeen := make(map[BlockPointer]bool) 1226 for _, Path := range pfr { 1227 pathLoop: 1228 for _, pb := range Path { 1229 for i := 0; i < pb.pblock.NumIndirectPtrs(); i++ { 1230 info, _ := pb.pblock.IndirectPtr(i) 1231 if infoSeen[info.BlockPointer] { 1232 // No need to iterate through this whole block 1233 // again if we've already seen one of its children 1234 // before. 1235 continue pathLoop 1236 } 1237 1238 infoSeen[info.BlockPointer] = true 1239 blockInfos = append(blockInfos, info) 1240 } 1241 } 1242 } 1243 return blockInfos, nil 1244 } 1245 1246 func (bt *blockTree) getIndirectBlockInfos(ctx context.Context) ( 1247 []BlockInfo, error) { 1248 if bt.rootBlockPointer().DirectType == DirectBlock { 1249 return nil, nil 1250 } 1251 1252 topBlock, _, err := bt.getter( 1253 ctx, bt.kmd, bt.rootBlockPointer(), bt.file, BlockRead) 1254 if err != nil { 1255 return nil, err 1256 } 1257 return bt.getIndirectBlockInfosWithTopBlock(ctx, topBlock) 1258 }