github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/cr_chains.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 libkbfs 6 7 import ( 8 "fmt" 9 "sort" 10 "time" 11 12 "github.com/keybase/client/go/kbfs/data" 13 "github.com/keybase/client/go/kbfs/idutil" 14 "github.com/keybase/client/go/kbfs/kbfscodec" 15 "github.com/keybase/client/go/kbfs/kbfscrypto" 16 "github.com/keybase/client/go/kbfs/kbfsmd" 17 "github.com/keybase/client/go/kbfs/libkey" 18 "github.com/keybase/client/go/kbfs/tlf" 19 "github.com/keybase/client/go/logger" 20 "github.com/keybase/client/go/protocol/keybase1" 21 "github.com/pkg/errors" 22 "golang.org/x/net/context" 23 ) 24 25 // crChain represents the set of operations that happened to a 26 // particular KBFS node (e.g., individual file or directory) over a 27 // given set of MD updates. It also tracks the starting and ending 28 // block pointers for the node. 29 type crChain struct { 30 ops []op 31 original, mostRecent data.BlockPointer 32 file bool 33 obfuscator data.Obfuscator 34 tlfID tlf.ID 35 } 36 37 // collapse finds complementary pairs of operations that cancel each 38 // other out, and remove the relevant operations from the chain. 39 // Examples include: 40 // - A create followed by a remove for the same name (delete both ops) 41 // - A create followed by a create (renamed == true) for the same name 42 // (delete the create op) 43 // - A remove that only unreferences blocks created within this branch 44 // 45 // This function returns the list of pointers that should be unreferenced 46 // as part of an eventual resolution of the corresponding branch. 47 func (cc *crChain) collapse(createdOriginals map[data.BlockPointer]bool, 48 originals map[data.BlockPointer]data.BlockPointer) (toUnrefs []data.BlockPointer) { 49 createsSeen := make(map[string]int) 50 indicesToRemove := make(map[int]bool) 51 var wr []WriteRange 52 lastSyncOp := -1 53 var syncRefs, syncUnrefs []data.BlockPointer 54 for i, op := range cc.ops { 55 switch realOp := op.(type) { 56 case *createOp: 57 if prevCreateIndex, ok := 58 createsSeen[realOp.NewName]; realOp.renamed && ok { 59 // A rename has papered over the first create, so 60 // just drop it. 61 indicesToRemove[prevCreateIndex] = true 62 } 63 createsSeen[realOp.NewName] = i 64 case *rmOp: 65 if prevCreateIndex, ok := createsSeen[realOp.OldName]; ok { 66 delete(createsSeen, realOp.OldName) 67 // The rm cancels out the create, so remove it. 68 indicesToRemove[prevCreateIndex] = true 69 // Also remove the rmOp if it was part of a rename 70 // (i.e., it wasn't a "real" rm), or if it otherwise 71 // only unreferenced blocks that were created on this 72 // branch. 73 doRemove := true 74 for _, unref := range op.Unrefs() { 75 original, ok := originals[unref] 76 if !ok { 77 original = unref 78 } 79 if !createdOriginals[original] { 80 doRemove = false 81 break 82 } 83 } 84 if doRemove { 85 indicesToRemove[i] = true 86 toUnrefs = append(toUnrefs, op.Unrefs()...) 87 } 88 } 89 case *setAttrOp: 90 // TODO: Collapse opposite setex pairs 91 case *syncOp: 92 wr = realOp.collapseWriteRange(wr) 93 indicesToRemove[i] = true 94 lastSyncOp = i 95 // The last op will have its refs listed twice in the 96 // collapsed op, but that's harmless. 97 syncRefs = append(syncRefs, op.Refs()...) 98 // The last op will have its unrefs listed twice in the 99 // collapsed op, but that's harmless. 100 syncUnrefs = append(syncUnrefs, op.Unrefs()...) 101 default: 102 // ignore other op types 103 } 104 } 105 106 if len(indicesToRemove) > 0 { 107 ops := make([]op, 0, len(cc.ops)-len(indicesToRemove)) 108 for i, op := range cc.ops { 109 if i == lastSyncOp { 110 so, ok := op.(*syncOp) 111 if !ok { 112 panic(fmt.Sprintf( 113 "Op %s at index %d should have been a syncOp", op, i)) 114 } 115 so.Writes = wr 116 for _, ref := range syncRefs { 117 op.AddRefBlock(ref) 118 } 119 for _, unref := range syncUnrefs { 120 op.AddUnrefBlock(unref) 121 } 122 ops = append(ops, op) 123 } else if !indicesToRemove[i] { 124 ops = append(ops, op) 125 } 126 } 127 cc.ops = ops 128 } 129 return toUnrefs 130 } 131 132 func (cc *crChain) getCollapsedWriteRange() []WriteRange { 133 if !cc.isFile() { 134 return nil 135 } 136 var wr []WriteRange 137 for _, op := range cc.ops { 138 syncOp, ok := op.(*syncOp) 139 if !ok { 140 continue 141 } 142 wr = syncOp.collapseWriteRange(wr) 143 } 144 return wr 145 } 146 147 func writeRangesEquivalent( 148 wr1 []WriteRange, wr2 []WriteRange) bool { 149 // Both empty? 150 if len(wr1) == 0 && len(wr2) == 0 { 151 return true 152 } 153 154 // If both branches contain no writes, and their truncation 155 // points are the same, then there are no unmerged actions to 156 // take. 157 if len(wr1) == 1 && wr1[0].isTruncate() && 158 len(wr2) == 1 && wr2[0].isTruncate() && 159 wr1[0].Off == wr2[0].Off { 160 return true 161 } 162 163 // TODO: In the future we may be able to do smarter merging 164 // here if the write ranges don't overlap, though maybe only 165 // for certain file types? 166 return false 167 } 168 169 func (cc *crChain) removeSyncOps() { 170 var newOps []op 171 for _, op := range cc.ops { 172 if _, ok := op.(*syncOp); !ok { 173 newOps = append(newOps, op) 174 } 175 } 176 177 cc.ops = newOps 178 } 179 180 func (cc *crChain) getActionsToMerge( 181 ctx context.Context, renamer ConflictRenamer, mergedPath data.Path, 182 mergedChain *crChain) (crActionList, error) { 183 var actions crActionList 184 185 // If this is a file, determine whether the unmerged chain 186 // could actually have changed the file in some way that it 187 // hasn't already been changed. For example, if they both 188 // truncate the file to the same length, and there are no 189 // other writes, we can just drop the unmerged syncs. 190 if cc.isFile() && mergedChain != nil { 191 // The write ranges should already be collapsed into a single 192 // syncOp, these calls just find that one remaining syncOp. 193 myWriteRange := cc.getCollapsedWriteRange() 194 mergedWriteRange := mergedChain.getCollapsedWriteRange() 195 196 if writeRangesEquivalent(myWriteRange, mergedWriteRange) { 197 // drop all sync ops 198 cc.removeSyncOps() 199 } 200 } 201 202 // Check each op against all ops in the corresponding merged 203 // chain, looking for conflicts. If there is a conflict, return 204 // it as part of the action list. If there are no conflicts for 205 // that op, return the op's default actions. 206 for _, unmergedOp := range cc.ops { 207 conflict := false 208 if mergedChain != nil { 209 for _, mergedOp := range mergedChain.ops { 210 action, err := 211 unmergedOp.checkConflict( 212 ctx, renamer, mergedOp, cc.isFile()) 213 if err != nil { 214 return nil, err 215 } 216 if action != nil { 217 conflict = true 218 actions = append(actions, action) 219 } 220 } 221 } 222 // no conflicts! 223 if !conflict { 224 action := unmergedOp.getDefaultAction(mergedPath) 225 if action != nil { 226 actions = append(actions, action) 227 } 228 } 229 } 230 231 return actions, nil 232 } 233 234 func (cc *crChain) isFile() bool { 235 return cc.file 236 } 237 238 // identifyType figures out whether this chain represents a file or 239 // directory. It tries to figure it out based purely on operation 240 // state, but setAttr(mtime) can apply to either type; in that case, 241 // we need to fetch the block to figure out the type. 242 func (cc *crChain) identifyType(ctx context.Context, fbo *folderBlockOps, 243 kmd libkey.KeyMetadata, chains *crChains) error { 244 if len(cc.ops) == 0 { 245 return nil 246 } 247 248 // If any op is setAttr (ex or size) or sync, this is a file 249 // chain. If it only has a setAttr/mtime, we don't know what it 250 // is, so fall through and fetch the block unless we come across 251 // another op that can determine the type. 252 var parentDir data.BlockPointer 253 var lastSetAttr *setAttrOp 254 for _, op := range cc.ops { 255 switch realOp := op.(type) { 256 case *syncOp: 257 cc.file = true 258 return nil 259 case *setAttrOp: 260 if realOp.Attr != mtimeAttr { 261 cc.file = true 262 return nil 263 } 264 // We can't tell the file type from an mtimeAttr, so we 265 // may have to actually fetch the block to figure it out. 266 parentDir = realOp.Dir.Ref 267 lastSetAttr = realOp 268 default: 269 return nil 270 } 271 } 272 273 parentOriginal, ok := chains.originals[parentDir] 274 if !ok { 275 // If the parent dir was created as part of a squash/batch, 276 // there might not be any update for it, and so it might not 277 // appear in the `originals` map. In that case, we can use 278 // the original. 279 parentOriginal = parentDir 280 ok = chains.createdOriginals[parentDir] 281 } 282 if !ok { 283 if chains.isDeleted(parentDir) { 284 // If the parent's been deleted, it doesn't matter whether 285 // we find the type or not. 286 return nil 287 } 288 fbo.log.CDebugf(ctx, 289 "Can't find parent dir chain, unref=%s/ref=%s, for op %s (file=%s)", 290 lastSetAttr.Dir.Unref, lastSetAttr.Dir.Ref, 291 lastSetAttr, lastSetAttr.File) 292 293 // If the parent still can't be found, then barring bugs the 294 // whole directory was created and deleted within this update, 295 // so it should be treated as deleted. 296 chains.deletedOriginals[cc.original] = true 297 return nil 298 } 299 300 // We have to find the current parent directory block. If the 301 // file has been renamed, that might be different from parentDir 302 // above. 303 if newParent, _, ok := chains.renamedParentAndName(cc.original); ok { 304 parentOriginal = newParent 305 } 306 307 parentMostRecent, err := chains.mostRecentFromOriginalOrSame(parentOriginal) 308 if err != nil { 309 return err 310 } 311 312 // If we get down here, we have an ambiguity, and need to fetch 313 // the block to figure out the file type. 314 parentPath := data.Path{ 315 FolderBranch: fbo.folderBranch, 316 Path: []data.PathNode{{ 317 BlockPointer: parentMostRecent, 318 Name: data.PathPartString{}, 319 }}, 320 } 321 parentDD, cleanupFn := fbo.newDirDataWithDBM( 322 makeFBOLockState(), parentPath, keybase1.UserOrTeamID(""), kmd, 323 newDirBlockMapMemory()) 324 defer cleanupFn() 325 entries, err := parentDD.GetEntries(ctx) 326 if err != nil { 327 return err 328 } 329 // We don't have the file name handy, so search for the pointer. 330 found := false 331 for _, entry := range entries { 332 if entry.BlockPointer != cc.mostRecent { 333 continue 334 } 335 switch entry.Type { 336 case data.Dir: 337 cc.file = false 338 case data.File: 339 cc.file = true 340 case data.Exec: 341 cc.file = true 342 default: 343 return errors.Errorf("Unexpected chain type: %s", entry.Type) 344 } 345 found = true 346 break 347 } 348 349 if !found { 350 // If the node can't be found, then the entry has been removed 351 // already, and there won't be any conflicts to resolve 352 // anyway. Mark it as deleted. 353 chains.deletedOriginals[cc.original] = true 354 355 // However, we still might be able to determine the type of 356 // the entry via the `rmOp` that actually deleted it. This 357 // could be important if the entry ends up being recreated due 358 // to a conflict with another branch (e.g. HOTPOT-719). So we 359 // still want to make an attempt to recover that entry type 360 // from the parent chain. 361 parentChain, ok := chains.byOriginal[parentOriginal] 362 if ok { 363 for _, op := range parentChain.ops { 364 rop, ok := op.(*rmOp) 365 if !ok { 366 continue 367 } 368 unrefs := rop.Unrefs() 369 if len(unrefs) > 0 && unrefs[0] == cc.mostRecent { 370 cc.file = rop.RemovedType == data.File || 371 rop.RemovedType == data.Exec 372 break 373 } 374 } 375 } 376 } 377 378 return nil 379 } 380 381 func (cc *crChain) remove(ctx context.Context, log logger.Logger, 382 revision kbfsmd.Revision) bool { 383 anyRemoved := false 384 var newOps []op 385 for i, currOp := range cc.ops { 386 info := currOp.getWriterInfo() 387 if info.revision == revision { 388 log.CDebugf(ctx, "Removing op %s from chain with mostRecent=%v", 389 currOp, cc.mostRecent) 390 if !anyRemoved { 391 newOps = make([]op, i, len(cc.ops)-1) 392 // Copy everything we've iterated over so far. 393 copy(newOps[:i], cc.ops[:i]) 394 anyRemoved = true 395 } 396 } else if anyRemoved { 397 newOps = append(newOps, currOp) 398 } 399 } 400 if anyRemoved { 401 cc.ops = newOps 402 } 403 return anyRemoved 404 } 405 406 func (cc *crChain) hasSyncOp() bool { 407 for _, op := range cc.ops { 408 if _, ok := op.(*syncOp); ok { 409 return true 410 } 411 } 412 return false 413 } 414 415 func (cc *crChain) hasSetAttrOp() bool { 416 for _, op := range cc.ops { 417 if _, ok := op.(*setAttrOp); ok { 418 return true 419 } 420 } 421 return false 422 } 423 424 func (cc *crChain) ensurePath(op op, ptr data.BlockPointer) { 425 if op.getFinalPath().IsValid() { 426 return 427 } 428 429 // Use the same obfuscator for both the node's name and it's 430 // children, because it's too complicated to try to get the real 431 // parent obfuscator from the chain. 432 op.setFinalPath(data.Path{ 433 FolderBranch: data.FolderBranch{ 434 Tlf: cc.tlfID, 435 Branch: data.MasterBranch, 436 }, 437 Path: []data.PathNode{{ 438 BlockPointer: ptr, 439 Name: data.NewPathPartString("", cc.obfuscator), 440 }}, 441 ChildObfuscator: cc.obfuscator, 442 }) 443 } 444 445 type renameInfo struct { 446 originalOldParent data.BlockPointer 447 oldName string 448 originalNewParent data.BlockPointer 449 newName string 450 } 451 452 func (ri renameInfo) String() string { 453 return fmt.Sprintf( 454 "renameInfo{originalOldParent: %s, oldName: %s, originalNewParent: %s, newName: %s}", 455 ri.originalOldParent, ri.oldName, 456 ri.originalNewParent, ri.newName) 457 } 458 459 // crChains contains a crChain for every KBFS node affected by the 460 // operations over a given set of MD updates. The chains are indexed 461 // by both the starting (original) and ending (most recent) pointers. 462 // It also keeps track of which chain points to the root of the folder. 463 type crChains struct { 464 byOriginal map[data.BlockPointer]*crChain 465 byMostRecent map[data.BlockPointer]*crChain 466 originalRoot data.BlockPointer 467 468 // The original blockpointers for nodes that have been 469 // unreferenced or initially referenced during this chain. 470 deletedOriginals map[data.BlockPointer]bool 471 createdOriginals map[data.BlockPointer]bool 472 473 // A map from original blockpointer to the full rename operation 474 // of the node (from the original location of the node to the 475 // final locations). 476 renamedOriginals map[data.BlockPointer]renameInfo 477 478 // Separately track pointers for unembedded block changes. 479 blockChangePointers map[data.BlockPointer]bool 480 481 // Pointers that should be explicitly cleaned up in the resolution. 482 toUnrefPointers map[data.BlockPointer]bool 483 484 // Pointers that should explicitly *not* be cleaned up in the 485 // resolution. 486 doNotUnrefPointers map[data.BlockPointer]bool 487 488 // Also keep the info for the most recent chain MD used to 489 // build these chains. 490 mostRecentChainMDInfo KeyMetadataWithRootDirEntry 491 492 // We need to be able to track ANY BlockPointer, at any point in 493 // the chain, back to its original. 494 originals map[data.BlockPointer]data.BlockPointer 495 496 // All the resolution ops from the branch, in order. 497 resOps []*resolutionOp 498 499 // Make per-chain obfuscators. 500 makeObfuscator func() data.Obfuscator 501 } 502 503 func (ccs *crChains) addOp(ptr data.BlockPointer, op op) error { 504 currChain, ok := ccs.byMostRecent[ptr] 505 if !ok { 506 return errors.Errorf("Could not find chain for most recent ptr %v", ptr) 507 } 508 509 // Make sure this op has a valid path with an obfuscator. 510 currChain.ensurePath(op, ptr) 511 currChain.ops = append(currChain.ops, op) 512 return nil 513 } 514 515 // addNoopChain adds a new chain with no ops to the chains struct, if 516 // that pointer isn't involved in any chains yet. 517 func (ccs *crChains) addNoopChain(ptr data.BlockPointer) { 518 if _, ok := ccs.byMostRecent[ptr]; ok { 519 return 520 } 521 if _, ok := ccs.byOriginal[ptr]; ok { 522 return 523 } 524 if _, ok := ccs.originals[ptr]; ok { 525 return 526 } 527 chain := &crChain{ 528 original: ptr, 529 mostRecent: ptr, 530 obfuscator: ccs.makeObfuscator(), 531 tlfID: ccs.mostRecentChainMDInfo.TlfID(), 532 } 533 ccs.byOriginal[ptr] = chain 534 ccs.byMostRecent[ptr] = chain 535 } 536 537 func (ccs *crChains) makeChainForOp(op op) error { 538 // Ignore gc ops -- their unref semantics differ from the other 539 // ops. Note that this only matters for old gcOps: new gcOps 540 // only unref the block ID, and not the whole pointer, so they 541 // wouldn't confuse chain creation. 542 if _, isGCOp := op.(*GCOp); isGCOp { 543 return nil 544 } 545 546 // First set the pointers for all updates, and track what's been 547 // created and destroyed. 548 for _, update := range op.allUpdates() { 549 chain, ok := ccs.byMostRecent[update.Unref] 550 if !ok { 551 // No matching chain means it's time to start a new chain 552 chain = &crChain{ 553 original: update.Unref, 554 obfuscator: ccs.makeObfuscator(), 555 tlfID: ccs.mostRecentChainMDInfo.TlfID(), 556 } 557 ccs.byOriginal[update.Unref] = chain 558 } 559 if chain.mostRecent.IsInitialized() { 560 // delete the old most recent pointer, it's no longer needed 561 delete(ccs.byMostRecent, chain.mostRecent) 562 } 563 chain.mostRecent = update.Ref 564 ccs.byMostRecent[update.Ref] = chain 565 if chain.original != update.Ref { 566 // Always be able to track this one back to its original. 567 ccs.originals[update.Ref] = chain.original 568 } 569 } 570 571 for _, ptr := range op.Refs() { 572 ccs.createdOriginals[ptr] = true 573 } 574 575 for _, ptr := range op.Unrefs() { 576 // Look up the original pointer corresponding to this most 577 // recent one. 578 original, ok := ccs.originals[ptr] 579 if !ok { 580 original = ptr 581 } 582 583 // If it has already been updated to something else, we should 584 // just ignore this unref. See KBFS-3258. 585 if chain, ok := ccs.byOriginal[original]; ok { 586 if ptr != chain.mostRecent { 587 continue 588 } 589 } 590 591 ccs.deletedOriginals[original] = true 592 } 593 594 // then set the op depending on the actual op type 595 switch realOp := op.(type) { 596 default: 597 panic(fmt.Sprintf("Unrecognized operation: %v", op)) 598 case *createOp: 599 err := ccs.addOp(realOp.Dir.Ref, op) 600 if err != nil { 601 return err 602 } 603 case *rmOp: 604 err := ccs.addOp(realOp.Dir.Ref, op) 605 if err != nil { 606 return err 607 } 608 609 if len(op.Unrefs()) == 0 { 610 // This might be an rmOp of a file that was created and 611 // removed within a single batch. If it's been renamed, 612 // we should also mark it as deleted to avoid confusing 613 // the CR code. 614 chain, ok := ccs.byMostRecent[realOp.Dir.Ref] 615 if !ok { 616 return errors.Errorf("No chain for rmOp dir %v", realOp.Dir.Ref) 617 } 618 for original, ri := range ccs.renamedOriginals { 619 if ri.originalNewParent == chain.original && 620 ri.newName == realOp.OldName { 621 ccs.deletedOriginals[original] = true 622 break 623 } 624 } 625 } 626 627 case *renameOp: 628 // split rename op into two separate operations, one for 629 // remove and one for create 630 ro, err := newRmOp( 631 realOp.OldName, realOp.OldDir.Unref, realOp.RenamedType) 632 if err != nil { 633 return err 634 } 635 ro.setWriterInfo(realOp.getWriterInfo()) 636 ro.setLocalTimestamp(realOp.getLocalTimestamp()) 637 // realOp.OldDir.Ref may be zero if this is a 638 // post-resolution chain, so set ro.Dir.Ref manually. 639 ro.Dir.Ref = realOp.OldDir.Ref 640 err = ccs.addOp(realOp.OldDir.Ref, ro) 641 if err != nil { 642 return err 643 } 644 645 ndu := realOp.NewDir.Unref 646 ndr := realOp.NewDir.Ref 647 if realOp.NewDir == (blockUpdate{}) { 648 // this is a rename within the same directory 649 ndu = realOp.OldDir.Unref 650 ndr = realOp.OldDir.Ref 651 } 652 653 if len(realOp.Unrefs()) > 0 { 654 // Something was overwritten; make an explicit rm for it 655 // so we can check for conflicts. 656 roOverwrite, err := newRmOp( 657 realOp.NewName, ndu, data.File) 658 if err != nil { 659 return err 660 } 661 roOverwrite.setWriterInfo(realOp.getWriterInfo()) 662 err = roOverwrite.Dir.setRef(ndr) 663 if err != nil { 664 return err 665 } 666 err = ccs.addOp(ndr, roOverwrite) 667 if err != nil { 668 return err 669 } 670 // Transfer any unrefs over. 671 for _, ptr := range realOp.Unrefs() { 672 roOverwrite.AddUnrefBlock(ptr) 673 } 674 } 675 676 co, err := newCreateOp(realOp.NewName, ndu, realOp.RenamedType) 677 if err != nil { 678 return err 679 } 680 co.setWriterInfo(realOp.getWriterInfo()) 681 co.setLocalTimestamp(realOp.getLocalTimestamp()) 682 co.renamed = true 683 // ndr may be zero if this is a post-resolution chain, 684 // so set co.Dir.Ref manually. 685 co.Dir.Ref = ndr 686 err = ccs.addOp(ndr, co) 687 if err != nil { 688 return err 689 } 690 691 // also keep track of the new parent for the renamed node 692 if realOp.Renamed.IsInitialized() { 693 newParentChain, ok := ccs.byMostRecent[ndr] 694 if !ok { 695 return errors.Errorf("While renaming, couldn't find the chain "+ 696 "for the new parent %v", ndr) 697 } 698 oldParentChain, ok := ccs.byMostRecent[realOp.OldDir.Ref] 699 if !ok { 700 return errors.Errorf("While renaming, couldn't find the chain "+ 701 "for the old parent %v", ndr) 702 } 703 704 renamedOriginal := realOp.Renamed 705 if renamedChain, ok := ccs.byMostRecent[realOp.Renamed]; ok { 706 renamedOriginal = renamedChain.original 707 } 708 // Use the previous old info if there is one already, 709 // in case this node has been renamed multiple times. 710 ri, ok := ccs.renamedOriginals[renamedOriginal] 711 if !ok { 712 // Otherwise make a new one. 713 ri = renameInfo{ 714 originalOldParent: oldParentChain.original, 715 oldName: realOp.OldName, 716 } 717 } 718 ri.originalNewParent = newParentChain.original 719 ri.newName = realOp.NewName 720 ccs.renamedOriginals[renamedOriginal] = ri 721 // Remember what you create, in case we need to merge 722 // directories after a rename. 723 co.AddRefBlock(renamedOriginal) 724 } 725 case *syncOp: 726 err := ccs.addOp(realOp.File.Ref, op) 727 if err != nil { 728 return err 729 } 730 case *setAttrOp: 731 // Because the attributes apply to the file, which doesn't 732 // actually have an updated pointer, we may need to create a 733 // new chain. 734 _, ok := ccs.byMostRecent[realOp.File] 735 if !ok { 736 // pointer didn't change, so most recent is the same: 737 chain := &crChain{ 738 original: realOp.File, 739 mostRecent: realOp.File, 740 obfuscator: ccs.makeObfuscator(), 741 tlfID: ccs.mostRecentChainMDInfo.TlfID(), 742 } 743 ccs.byOriginal[realOp.File] = chain 744 ccs.byMostRecent[realOp.File] = chain 745 } 746 747 err := ccs.addOp(realOp.File, op) 748 if err != nil { 749 return err 750 } 751 case *resolutionOp: 752 ccs.resOps = append(ccs.resOps, realOp) 753 case *rekeyOp: 754 // ignore rekey op 755 case *GCOp: 756 // ignore gc op 757 } 758 759 return nil 760 } 761 762 func (ccs *crChains) makeChainForNewOpWithUpdate( 763 targetPtr data.BlockPointer, newOp op, update *blockUpdate) error { 764 oldUpdate := *update 765 // so that most recent == original 766 var err error 767 *update, err = makeBlockUpdate(targetPtr, update.Unref) 768 if err != nil { 769 return err 770 } 771 defer func() { 772 // reset the update to its original state before returning. 773 *update = oldUpdate 774 }() 775 err = ccs.makeChainForOp(newOp) 776 if err != nil { 777 return err 778 } 779 return nil 780 } 781 782 // makeChainForNewOp makes a new chain for an op that does not yet 783 // have its pointers initialized. It does so by setting Unref and Ref 784 // to be the same for the duration of this function, and calling the 785 // usual makeChainForOp method. This function is not goroutine-safe 786 // with respect to newOp. Also note that rename ops will not be split 787 // into two ops; they will be placed only in the new directory chain. 788 func (ccs *crChains) makeChainForNewOp(targetPtr data.BlockPointer, newOp op) error { 789 switch realOp := newOp.(type) { 790 case *createOp: 791 return ccs.makeChainForNewOpWithUpdate(targetPtr, newOp, &realOp.Dir) 792 case *rmOp: 793 return ccs.makeChainForNewOpWithUpdate(targetPtr, newOp, &realOp.Dir) 794 case *renameOp: 795 // In this case, we don't want to split the rename chain, so 796 // just make up a new operation and later overwrite it with 797 // the rename op. 798 co, err := newCreateOp(realOp.NewName, realOp.NewDir.Unref, data.File) 799 if err != nil { 800 return err 801 } 802 err = ccs.makeChainForNewOpWithUpdate(targetPtr, co, &co.Dir) 803 if err != nil { 804 return err 805 } 806 chain, ok := ccs.byMostRecent[targetPtr] 807 if !ok { 808 return errors.Errorf("Couldn't find chain for %v after making it", 809 targetPtr) 810 } 811 if len(chain.ops) != 1 { 812 return errors.Errorf("Chain of unexpected length for %v after "+ 813 "making it", targetPtr) 814 } 815 chain.ops[0] = realOp 816 return nil 817 case *setAttrOp: 818 return ccs.makeChainForNewOpWithUpdate(targetPtr, newOp, &realOp.Dir) 819 case *syncOp: 820 return ccs.makeChainForNewOpWithUpdate(targetPtr, newOp, &realOp.File) 821 default: 822 return errors.Errorf("Couldn't make chain with unknown operation %s", 823 newOp) 824 } 825 } 826 827 func (ccs *crChains) mostRecentFromOriginal(original data.BlockPointer) ( 828 data.BlockPointer, error) { 829 chain, ok := ccs.byOriginal[original] 830 if !ok { 831 return data.BlockPointer{}, errors.WithStack(NoChainFoundError{original}) 832 } 833 return chain.mostRecent, nil 834 } 835 836 func (ccs *crChains) mostRecentFromOriginalOrSame(original data.BlockPointer) ( 837 data.BlockPointer, error) { 838 ptr, err := ccs.mostRecentFromOriginal(original) 839 if err == nil { 840 // A satisfactory chain was found. 841 return ptr, nil 842 } else if _, ok := errors.Cause(err).(NoChainFoundError); !ok { 843 // An unexpected error! 844 return data.BlockPointer{}, err 845 } 846 return original, nil 847 } 848 849 func (ccs *crChains) originalFromMostRecent(mostRecent data.BlockPointer) ( 850 data.BlockPointer, error) { 851 chain, ok := ccs.byMostRecent[mostRecent] 852 if !ok { 853 return data.BlockPointer{}, errors.WithStack(NoChainFoundError{mostRecent}) 854 } 855 return chain.original, nil 856 } 857 858 func (ccs *crChains) originalFromMostRecentOrSame(mostRecent data.BlockPointer) ( 859 data.BlockPointer, error) { 860 ptr, err := ccs.originalFromMostRecent(mostRecent) 861 if err == nil { 862 // A satisfactory chain was found. 863 return ptr, nil 864 } else if _, ok := errors.Cause(err).(NoChainFoundError); !ok { 865 // An unexpected error! 866 return data.BlockPointer{}, err 867 } 868 return mostRecent, nil 869 } 870 871 func (ccs *crChains) isCreated(original data.BlockPointer) bool { 872 return ccs.createdOriginals[original] 873 } 874 875 func (ccs *crChains) isDeleted(original data.BlockPointer) bool { 876 return ccs.deletedOriginals[original] 877 } 878 879 func (ccs *crChains) renamedParentAndName(original data.BlockPointer) ( 880 data.BlockPointer, string, bool) { 881 info, ok := ccs.renamedOriginals[original] 882 if !ok { 883 return data.BlockPointer{}, "", false 884 } 885 return info.originalNewParent, info.newName, true 886 } 887 888 func newCRChainsEmpty(makeObfuscator func() data.Obfuscator) *crChains { 889 return &crChains{ 890 byOriginal: make(map[data.BlockPointer]*crChain), 891 byMostRecent: make(map[data.BlockPointer]*crChain), 892 deletedOriginals: make(map[data.BlockPointer]bool), 893 createdOriginals: make(map[data.BlockPointer]bool), 894 renamedOriginals: make(map[data.BlockPointer]renameInfo), 895 blockChangePointers: make(map[data.BlockPointer]bool), 896 toUnrefPointers: make(map[data.BlockPointer]bool), 897 doNotUnrefPointers: make(map[data.BlockPointer]bool), 898 originals: make(map[data.BlockPointer]data.BlockPointer), 899 makeObfuscator: makeObfuscator, 900 } 901 } 902 903 func (ccs *crChains) addOps(codec kbfscodec.Codec, 904 privateMD PrivateMetadata, winfo writerInfo, 905 localTimestamp time.Time) error { 906 // Copy the ops since CR will change them. 907 var oldOps opsList 908 if privateMD.Changes.Info.BlockPointer.IsInitialized() { 909 // In some cases (e.g., journaling) we might not have been 910 // able to re-embed the block changes. So use the cached 911 // version directly. 912 oldOps = privateMD.cachedChanges.Ops 913 } else { 914 oldOps = privateMD.Changes.Ops 915 } 916 ops := make(opsList, len(oldOps)) 917 err := kbfscodec.Update(codec, &ops, oldOps) 918 if err != nil { 919 return err 920 } 921 922 for i, op := range ops { 923 op.setFinalPath(oldOps[i].getFinalPath()) 924 op.setWriterInfo(winfo) 925 op.setLocalTimestamp(localTimestamp) 926 err := ccs.makeChainForOp(op) 927 if err != nil { 928 return err 929 } 930 } 931 return nil 932 } 933 934 // chainMetadata is the interface for metadata objects that can be 935 // used in building crChains. It is implemented by 936 // ImmutableRootMetadata, but is also mostly implemented by 937 // RootMetadata (just need LastModifyingWriterVerifyingKey 938 // LocalTimestamp). 939 type chainMetadata interface { 940 KeyMetadataWithRootDirEntry 941 IsWriterMetadataCopiedSet() bool 942 LastModifyingWriter() keybase1.UID 943 LastModifyingWriterVerifyingKey() kbfscrypto.VerifyingKey 944 Revision() kbfsmd.Revision 945 Data() *PrivateMetadata 946 LocalTimestamp() time.Time 947 } 948 949 // newCRChains builds a new crChains object from the given list of 950 // chainMetadatas, which must be non-empty. 951 func newCRChains( 952 ctx context.Context, codec kbfscodec.Codec, osg idutil.OfflineStatusGetter, 953 chainMDs []chainMetadata, fbo *folderBlockOps, identifyTypes bool) ( 954 ccs *crChains, err error) { 955 if fbo != nil { 956 ccs = newCRChainsEmpty(fbo.obfuscatorMaker()) 957 } else { 958 ccs = newCRChainsEmpty(func() data.Obfuscator { return nil }) 959 } 960 961 mostRecentMD := chainMDs[len(chainMDs)-1] 962 ccs.mostRecentChainMDInfo = mostRecentMD 963 964 // For each MD update, turn each update in each op into map 965 // entries and create chains for the BlockPointers that are 966 // affected directly by the operation. 967 for _, chainMD := range chainMDs { 968 // No new operations in these. 969 if chainMD.IsWriterMetadataCopiedSet() { 970 continue 971 } 972 973 offline := keybase1.OfflineAvailability_NONE 974 if osg != nil { 975 offline = osg.OfflineAvailabilityForID(chainMD.TlfID()) 976 } 977 978 winfo := newWriterInfo( 979 chainMD.LastModifyingWriter(), 980 chainMD.LastModifyingWriterVerifyingKey(), 981 chainMD.Revision(), offline) 982 if err != nil { 983 return nil, err 984 } 985 986 chainData := *chainMD.Data() 987 988 err = ccs.addOps(codec, chainData, winfo, chainMD.LocalTimestamp()) 989 990 if ptr := chainData.cachedChanges.Info.BlockPointer; ptr != data.ZeroPtr { 991 ccs.blockChangePointers[ptr] = true 992 993 // Any child block change pointers? 994 infos, err := fbo.GetIndirectFileBlockInfos( 995 ctx, makeFBOLockState(), chainMD, 996 data.Path{ 997 FolderBranch: fbo.folderBranch, 998 Path: []data.PathNode{{ 999 BlockPointer: ptr, 1000 Name: data.NewPathPartString( 1001 fmt.Sprintf("<MD rev %d>", chainMD.Revision()), 1002 nil), 1003 }}, 1004 }) 1005 if err != nil { 1006 return nil, err 1007 } 1008 for _, info := range infos { 1009 ccs.blockChangePointers[info.BlockPointer] = true 1010 } 1011 } 1012 1013 if err != nil { 1014 return nil, err 1015 } 1016 1017 if !ccs.originalRoot.IsInitialized() { 1018 // Find the original pointer for the root directory 1019 if rootChain, ok := 1020 ccs.byMostRecent[chainData.Dir.BlockPointer]; ok { 1021 ccs.originalRoot = rootChain.original 1022 } 1023 } 1024 } 1025 1026 for _, chain := range ccs.byOriginal { 1027 toUnrefs := chain.collapse(ccs.createdOriginals, ccs.originals) 1028 for _, unref := range toUnrefs { 1029 ccs.toUnrefPointers[unref] = true 1030 } 1031 // NOTE: even if we've removed all its ops, still keep the 1032 // chain around so we can see the mapping between the original 1033 // and most recent pointers. 1034 1035 // Figure out if this chain is a file or directory. We don't 1036 // need to do this for chains that represent a resolution in 1037 // progress, since in that case all actions are already 1038 // completed. 1039 if identifyTypes { 1040 err := chain.identifyType(ctx, fbo, mostRecentMD, ccs) 1041 if err != nil { 1042 return nil, err 1043 } 1044 } 1045 } 1046 1047 return ccs, nil 1048 } 1049 1050 // newCRChainsForIRMDs simply builds a list of chainMetadatas from the 1051 // given list of ImmutableRootMetadatas and calls newCRChains with it. 1052 func newCRChainsForIRMDs( 1053 ctx context.Context, codec kbfscodec.Codec, osg idutil.OfflineStatusGetter, 1054 irmds []ImmutableRootMetadata, fbo *folderBlockOps, 1055 identifyTypes bool) (ccs *crChains, err error) { 1056 chainMDs := make([]chainMetadata, len(irmds)) 1057 for i, irmd := range irmds { 1058 chainMDs[i] = irmd 1059 } 1060 return newCRChains(ctx, codec, osg, chainMDs, fbo, identifyTypes) 1061 } 1062 1063 type crChainSummary struct { 1064 Path string 1065 Ops []string 1066 } 1067 1068 func (ccs *crChains) summary(identifyChains *crChains, 1069 nodeCache NodeCache) (res []*crChainSummary) { 1070 for _, chain := range ccs.byOriginal { 1071 summary := &crChainSummary{} 1072 res = append(res, summary) 1073 1074 // first stringify all the ops so they are displayed even if 1075 // we can't find the path. 1076 for _, op := range chain.ops { 1077 summary.Ops = append(summary.Ops, op.String()) 1078 } 1079 1080 // find the path name using the identified most recent pointer 1081 n := nodeCache.Get(chain.mostRecent.Ref()) 1082 if n == nil { 1083 summary.Path = fmt.Sprintf("Unknown path: %v", chain.mostRecent) 1084 continue 1085 } 1086 1087 path := nodeCache.PathFromNode(n) 1088 summary.Path = path.String() 1089 } 1090 1091 return res 1092 } 1093 1094 func (ccs *crChains) removeChain(ptr data.BlockPointer) { 1095 if chain, ok := ccs.byMostRecent[ptr]; ok { 1096 delete(ccs.byOriginal, chain.original) 1097 } else { 1098 delete(ccs.byOriginal, ptr) 1099 } 1100 delete(ccs.byMostRecent, ptr) 1101 } 1102 1103 // copyOpAndRevertUnrefsToOriginals returns a shallow copy of the op, 1104 // modifying each custom BlockPointer field to reference the original 1105 // version of the corresponding blocks. 1106 func (ccs *crChains) copyOpAndRevertUnrefsToOriginals(currOp op) op { 1107 var unrefs []*data.BlockPointer 1108 var newOp op 1109 switch realOp := currOp.(type) { 1110 case *createOp: 1111 newCreateOp := *realOp 1112 unrefs = append(unrefs, &newCreateOp.Dir.Unref) 1113 newOp = &newCreateOp 1114 case *rmOp: 1115 newRmOp := *realOp 1116 unrefs = append(unrefs, &newRmOp.Dir.Unref) 1117 newOp = &newRmOp 1118 case *renameOp: 1119 newRenameOp := *realOp 1120 unrefs = append(unrefs, &newRenameOp.OldDir.Unref, 1121 &newRenameOp.NewDir.Unref, &newRenameOp.Renamed) 1122 newOp = &newRenameOp 1123 case *syncOp: 1124 newSyncOp := *realOp 1125 unrefs = append(unrefs, &newSyncOp.File.Unref) 1126 newOp = &newSyncOp 1127 case *setAttrOp: 1128 newSetAttrOp := *realOp 1129 unrefs = append(unrefs, &newSetAttrOp.Dir.Unref, &newSetAttrOp.File) 1130 newOp = &newSetAttrOp 1131 case *GCOp: 1132 // No need to copy a GCOp, it won't be modified 1133 newOp = realOp 1134 case *rekeyOp: 1135 newOp = realOp 1136 } 1137 for _, unref := range unrefs { 1138 ok := true 1139 // Loop over the originals, since if `changeOriginal` was 1140 // called, there might be a path of them. 1141 for ok { 1142 var original data.BlockPointer 1143 original, ok = ccs.originals[*unref] 1144 if ok { 1145 *unref = original 1146 } 1147 } 1148 } 1149 return newOp 1150 } 1151 1152 // changeOriginal converts the original of a chain to a different 1153 // original, which originated in some other branch. 1154 func (ccs *crChains) changeOriginal(oldOriginal data.BlockPointer, 1155 newOriginal data.BlockPointer) error { 1156 if oldOriginal == newOriginal { 1157 // This apparently can happen, but I'm not sure how. (See 1158 // KBFS-2946.) Maybe because of a self-conflict in some weird 1159 // error conditions. In this case, we can just pretend the 1160 // change worked, and let CR continue it's normal process. 1161 return nil 1162 } 1163 chain, ok := ccs.byOriginal[oldOriginal] 1164 if !ok { 1165 return errors.WithStack(NoChainFoundError{oldOriginal}) 1166 } 1167 if _, ok := ccs.byOriginal[newOriginal]; ok { 1168 return errors.Errorf("crChains.changeOriginal: New original %v "+ 1169 "already exists", newOriginal) 1170 } 1171 1172 delete(ccs.byOriginal, oldOriginal) 1173 chain.original = newOriginal 1174 ccs.byOriginal[newOriginal] = chain 1175 ccs.originals[oldOriginal] = newOriginal 1176 1177 if _, ok := ccs.deletedOriginals[oldOriginal]; ok { 1178 delete(ccs.deletedOriginals, oldOriginal) 1179 ccs.deletedOriginals[newOriginal] = true 1180 } 1181 delete(ccs.createdOriginals, oldOriginal) 1182 // We're swapping in an original made on some other branch, so 1183 // it shouldn't go in the `createdOriginals` map. 1184 if ri, ok := ccs.renamedOriginals[oldOriginal]; ok { 1185 delete(ccs.renamedOriginals, oldOriginal) 1186 ccs.renamedOriginals[newOriginal] = ri 1187 } 1188 for p, info := range ccs.renamedOriginals { 1189 changed := false 1190 if info.originalOldParent == oldOriginal { 1191 info.originalOldParent = newOriginal 1192 changed = true 1193 } 1194 if info.originalNewParent == oldOriginal { 1195 info.originalNewParent = newOriginal 1196 changed = true 1197 } 1198 if changed { 1199 ccs.renamedOriginals[p] = info 1200 } 1201 } 1202 return nil 1203 } 1204 1205 func (ccs *crChains) findPathForDeleted(mostRecent data.BlockPointer) data.Path { 1206 // Find the parent chain that deleted this one. 1207 for ptr, chain := range ccs.byMostRecent { 1208 for _, op := range chain.ops { 1209 ro, ok := op.(*rmOp) 1210 if !ok { 1211 continue 1212 } 1213 for _, unref := range ro.Unrefs() { 1214 if unref == mostRecent { 1215 // If the path isn't set yet, recurse. 1216 p := ro.getFinalPath() 1217 if !p.IsValid() { 1218 p = ccs.findPathForDeleted(ptr) 1219 ro.setFinalPath(p) 1220 } 1221 return p.ChildPath( 1222 ro.obfuscatedOldName(), mostRecent, 1223 ccs.makeObfuscator()) 1224 } 1225 } 1226 } 1227 } 1228 // We can get here if the entry in question was also created 1229 // during the chain period, in which case `mostRecent` doesn't 1230 // need to be unreferenced explicitly. There's nothing easy we 1231 // can do here. But the path isn't very important; for the most 1232 // part, it's just informational for log messages and journal 1233 // status. So if we are stuck, just pick use the root directory 1234 // and a fake name. 1235 var rootMostRecent data.BlockPointer 1236 if ccs.mostRecentChainMDInfo != nil { 1237 rootMostRecent = 1238 ccs.mostRecentChainMDInfo.GetRootDirEntry().BlockPointer 1239 } 1240 return data.Path{ 1241 FolderBranch: data.FolderBranch{ 1242 Tlf: ccs.mostRecentChainMDInfo.TlfID(), 1243 Branch: data.MasterBranch, 1244 }, 1245 Path: []data.PathNode{{ 1246 BlockPointer: rootMostRecent, 1247 Name: data.NewPathPartString( 1248 mostRecent.String(), ccs.makeObfuscator()), 1249 }}, 1250 } 1251 } 1252 1253 func (ccs *crChains) findPathForCreated(createdChain *crChain) data.Path { 1254 mostRecent := createdChain.mostRecent 1255 1256 // Find the parent chain that deleted this one. 1257 for _, chain := range ccs.byMostRecent { 1258 for _, op := range chain.ops { 1259 co, ok := op.(*createOp) 1260 if !ok { 1261 continue 1262 } 1263 for _, ref := range co.Refs() { 1264 if ref == createdChain.original { 1265 // If the path isn't set yet, recurse. 1266 p := co.getFinalPath() 1267 if !p.IsValid() { 1268 p = ccs.findPathForCreated(chain) 1269 co.setFinalPath(p) 1270 } 1271 return p.ChildPath( 1272 co.obfuscatedNewName(), mostRecent, 1273 ccs.makeObfuscator()) 1274 } 1275 } 1276 } 1277 } 1278 // We can get here if the entry in question was also deleted 1279 // during the chain period, in which case `mostRecent` doesn't 1280 // need to be unreferenced explicitly. There's nothing easy we 1281 // can do here. But the path isn't very important; for the most 1282 // part, it's just informational for log messages and journal 1283 // status. So if we are stuck, just pick use the root directory 1284 // and a fake name. 1285 var rootMostRecent data.BlockPointer 1286 if ccs.mostRecentChainMDInfo != nil { 1287 rootMostRecent = 1288 ccs.mostRecentChainMDInfo.GetRootDirEntry().BlockPointer 1289 } 1290 return data.Path{ 1291 FolderBranch: data.FolderBranch{ 1292 Tlf: ccs.mostRecentChainMDInfo.TlfID(), 1293 Branch: data.MasterBranch, 1294 }, 1295 Path: []data.PathNode{{ 1296 BlockPointer: rootMostRecent, 1297 Name: data.NewPathPartString( 1298 mostRecent.String(), ccs.makeObfuscator()), 1299 }}, 1300 } 1301 } 1302 1303 // getPaths returns a sorted slice of most recent paths to all the 1304 // nodes in the given CR chains that were directly modified during a 1305 // branch, and which existed at both the start and the end of the 1306 // branch. This represents the paths that need to be checked for 1307 // conflicts. The paths are sorted by descending path length. It 1308 // uses nodeCache when looking up paths, which must at least contain 1309 // the most recent root node of the branch. Note that if a path 1310 // cannot be found, the corresponding chain is completely removed from 1311 // the set of CR chains. Set includeCreates to true if the returned 1312 // paths should include the paths of newly-created nodes. 1313 func (ccs *crChains) getPaths(ctx context.Context, blocks *folderBlockOps, 1314 log logger.Logger, nodeCache NodeCache, includeCreates bool, 1315 checkOpFinalPaths bool) ([]data.Path, error) { 1316 newPtrs := make(map[data.BlockPointer]bool) 1317 var ptrs []data.BlockPointer 1318 renameOps := make(map[data.BlockPointer][]*renameOp) 1319 for ptr, chain := range ccs.byMostRecent { 1320 newPtrs[ptr] = true 1321 // We only care about the paths for ptrs that are directly 1322 // affected by operations and were live through the entire 1323 // unmerged branch, or, if includeCreates was set, was created 1324 // and not deleted in the unmerged branch. 1325 if len(chain.ops) > 0 && 1326 (includeCreates || !ccs.isCreated(chain.original)) && 1327 !ccs.isDeleted(chain.original) { 1328 ptrs = append(ptrs, ptr) 1329 // Also resolve the old name for any renames, if needed. 1330 for _, op := range chain.ops { 1331 ro, ok := op.(*renameOp) 1332 if !ok { 1333 continue 1334 } 1335 1336 oldDirPtr := ro.OldDir.Ref 1337 if ro.NewDir != (blockUpdate{}) { 1338 if !newPtrs[oldDirPtr] { 1339 ptrs = append(ptrs, oldDirPtr) 1340 newPtrs[oldDirPtr] = true 1341 } 1342 renameOps[oldDirPtr] = append(renameOps[oldDirPtr], ro) 1343 } 1344 } 1345 } 1346 } 1347 1348 if checkOpFinalPaths { 1349 // If we plan to check all the paths, clear them out first. 1350 for _, chain := range ccs.byMostRecent { 1351 for _, op := range chain.ops { 1352 op.setFinalPath(data.Path{}) 1353 } 1354 } 1355 } 1356 1357 pathMap, err := blocks.SearchForPaths(ctx, nodeCache, ptrs, 1358 newPtrs, ccs.mostRecentChainMDInfo, 1359 ccs.mostRecentChainMDInfo.GetRootDirEntry().BlockPointer) 1360 if err != nil { 1361 return nil, err 1362 } 1363 1364 paths := make([]data.Path, 0, len(pathMap)) 1365 for ptr, p := range pathMap { 1366 if len(p.Path) == 0 { 1367 log.CDebugf(ctx, "Ignoring pointer with no found path: %v", ptr) 1368 ccs.removeChain(ptr) 1369 continue 1370 } 1371 paths = append(paths, p) 1372 1373 // update the unmerged final paths 1374 if chain, ok := ccs.byMostRecent[ptr]; ok { 1375 for _, op := range chain.ops { 1376 op.setFinalPath(p) 1377 } 1378 } 1379 for _, ro := range renameOps[ptr] { 1380 ro.oldFinalPath = p 1381 } 1382 } 1383 1384 // Fill in the paths for any deleted entries. 1385 for original := range ccs.deletedOriginals { 1386 chain, ok := ccs.byOriginal[original] 1387 if !ok { 1388 continue 1389 } 1390 p := ccs.findPathForDeleted(chain.mostRecent) 1391 for _, op := range chain.ops { 1392 op.setFinalPath(p) 1393 } 1394 } 1395 1396 // Even if `includeCreates` is false, we still might need to have 1397 // the final paths set on all the ops, later during conflict 1398 // resolution. For example, in the case where the same directory 1399 // is created in both the unmerged and merged branches, and the 1400 // child entries need to be compared for conflicts 1401 if !includeCreates { 1402 for original := range ccs.createdOriginals { 1403 chain, ok := ccs.byOriginal[original] 1404 if !ok { 1405 continue 1406 } 1407 p := ccs.findPathForCreated(chain) 1408 for _, op := range chain.ops { 1409 op.setFinalPath(p) 1410 } 1411 } 1412 } 1413 1414 if checkOpFinalPaths { 1415 for _, chain := range ccs.byMostRecent { 1416 for _, op := range chain.ops { 1417 if !op.getFinalPath().IsValid() { 1418 return nil, errors.Errorf( 1419 "Op %s doesn't have final path", op) 1420 } 1421 } 1422 } 1423 } 1424 1425 // Order by descending path length. 1426 sort.Sort(crSortedPaths(paths)) 1427 return paths, nil 1428 } 1429 1430 // remove deletes all operations associated with the given revision 1431 // from the chains. It leaves original block pointers in place 1432 // though, even when removing operations from the head of the chain. 1433 // It returns the set of chains with at least one operation removed. 1434 func (ccs *crChains) remove(ctx context.Context, log logger.Logger, 1435 revision kbfsmd.Revision) []*crChain { 1436 var chainsWithRemovals []*crChain 1437 for _, chain := range ccs.byOriginal { 1438 if chain.remove(ctx, log, revision) { 1439 chainsWithRemovals = append(chainsWithRemovals, chain) 1440 } 1441 } 1442 return chainsWithRemovals 1443 } 1444 1445 func (ccs *crChains) revertRenames(oldOps []op) error { 1446 for _, oldOp := range oldOps { 1447 if rop, ok := oldOp.(*renameOp); ok { 1448 // Replace the corresponding createOp, and remove the 1449 // rmOp. 1450 oldChain, ok := ccs.byMostRecent[rop.OldDir.Ref] 1451 if !ok { 1452 continue 1453 } 1454 found := false 1455 for i, oldOp := range oldChain.ops { 1456 if rmop, ok := oldOp.(*rmOp); ok && 1457 rmop.OldName == rop.OldName { 1458 rop.oldFinalPath = rmop.getFinalPath() 1459 found = true 1460 oldChain.ops = append( 1461 oldChain.ops[:i], oldChain.ops[i+1:]...) 1462 // The first rm should be the one that matches, as 1463 // earlier ones should have been collapsed away 1464 // from the chain. 1465 break 1466 } 1467 } 1468 1469 if !found || !rop.oldFinalPath.IsValid() { 1470 // We don't need to revert any renames without an 1471 // rmOp, because it was probably just created and 1472 // renamed within a single journal update. 1473 continue 1474 } 1475 1476 newChain := oldChain 1477 if rop.NewDir != (blockUpdate{}) { 1478 newChain, ok = ccs.byMostRecent[rop.NewDir.Ref] 1479 if !ok { 1480 // There was a corresponding rmOp, and the node 1481 // was renamed across directories, but for some 1482 // unknown reason we can't find the chain for the 1483 // new directory. 1484 return errors.Errorf( 1485 "Cannot find new directory %s for rename op: %s", 1486 rop.NewDir.Ref, rop) 1487 } 1488 } 1489 1490 added := false 1491 for i, newOp := range newChain.ops { 1492 if cop, ok := newOp.(*createOp); ok && 1493 cop.renamed && cop.NewName == rop.NewName { 1494 ropCopy := rop.deepCopy() 1495 ropCopy.setFinalPath(cop.getFinalPath()) 1496 newChain.ops[i] = ropCopy 1497 added = true 1498 break 1499 } 1500 } 1501 if !added { 1502 // If we didn't find the create op to replace, then 1503 // this node may have been renamed and then removed, 1504 // with the create op being eliminated in the process. 1505 // We need to keep the rename op there though if there 1506 // is a remove operation, so that any remove 1507 // operations within the renamed directory are 1508 // processed correctly (see HOTPOT-616). 1509 for _, newOp := range newChain.ops { 1510 if rmop, ok := newOp.(*rmOp); ok && 1511 rop.NewName == rmop.OldName { 1512 ropCopy := rop.deepCopy() 1513 ropCopy.setFinalPath(rmop.getFinalPath()) 1514 newChain.ops = append([]op{ropCopy}, newChain.ops...) 1515 break 1516 } 1517 } 1518 } 1519 } 1520 } 1521 return nil 1522 }