github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/ops.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 "context" 9 "fmt" 10 "reflect" 11 "strings" 12 "time" 13 14 "github.com/keybase/client/go/kbfs/data" 15 "github.com/keybase/client/go/kbfs/kbfscodec" 16 "github.com/keybase/client/go/kbfs/kbfscrypto" 17 "github.com/keybase/client/go/kbfs/kbfsedits" 18 "github.com/keybase/client/go/kbfs/kbfsmd" 19 "github.com/keybase/client/go/kbfs/tlf" 20 "github.com/keybase/client/go/protocol/keybase1" 21 "github.com/keybase/go-codec/codec" 22 "github.com/pkg/errors" 23 ) 24 25 // op represents a single file-system remote-sync operation. Note that 26 // ops store and marshal any filenames in plaintext fields, so the 27 // accessed fields must be handled carefully. `String()` prints 28 // obfuscated filenames, however. 29 type op interface { 30 AddRefBlock(ptr data.BlockPointer) 31 DelRefBlock(ptr data.BlockPointer) 32 AddUnrefBlock(ptr data.BlockPointer) 33 DelUnrefBlock(ptr data.BlockPointer) 34 AddUpdate(oldPtr data.BlockPointer, newPtr data.BlockPointer) 35 SizeExceptUpdates() uint64 36 allUpdates() []blockUpdate 37 Refs() []data.BlockPointer 38 Unrefs() []data.BlockPointer 39 String() string 40 Plaintext() string 41 StringWithRefs(indent string) string 42 setWriterInfo(writerInfo) 43 getWriterInfo() writerInfo 44 setFinalPath(p data.Path) 45 getFinalPath() data.Path 46 setLocalTimestamp(t time.Time) 47 getLocalTimestamp() time.Time 48 checkValid() error 49 deepCopy() op 50 // checkConflict compares the function's target op with the given 51 // op, and returns a resolution if one is needed (or nil 52 // otherwise). The resulting action (if any) assumes that this 53 // method's target op is the unmerged op, and the given op is the 54 // merged op. 55 checkConflict(ctx context.Context, 56 renamer ConflictRenamer, mergedOp op, isFile bool) ( 57 crAction, error) 58 // getDefaultAction should be called on an unmerged op only after 59 // all conflicts with the corresponding change have been checked, 60 // and it returns the action to take against the merged branch 61 // given that there are no conflicts. 62 getDefaultAction(mergedPath data.Path) crAction 63 64 // AddSelfUpdate adds an update from the given pointer to itself. 65 // This should be used when the caller doesn't yet know what the 66 // new block ID will be, but wants to "complete" the update as a 67 // signal to a future prepping process that the block needs to be 68 // processed/readied, at which point the real new pointer will be 69 // filled in. 70 AddSelfUpdate(ptr data.BlockPointer) 71 72 // ToEditNotification returns an edit notification if this op 73 // needs one, otherwise it returns nil. 74 ToEditNotification( 75 rev kbfsmd.Revision, revTime time.Time, device kbfscrypto.VerifyingKey, 76 uid keybase1.UID, tlfID tlf.ID) *kbfsedits.NotificationMessage 77 } 78 79 // op codes 80 const ( 81 createOpCode kbfscodec.ExtCode = iota + kbfscodec.ExtCodeOpsRangeStart 82 rmOpCode 83 renameOpCode 84 syncOpCode 85 setAttrOpCode 86 resolutionOpCode 87 rekeyOpCode 88 gcOpCode // for deleting old blocks during an MD history truncation 89 ) 90 91 // blockUpdate represents a block that was updated to have a new 92 // BlockPointer. 93 // 94 // NOTE: Don't add or modify anything in this struct without 95 // considering how old clients will handle them. 96 type blockUpdate struct { 97 // TODO: Ideally, we'd omit Unref or Ref if they're 98 // empty. However, we'd first have to verify that there's 99 // nothing that relies on either one of these fields to always 100 // be filled (e.g., see similar comments for the Info field on 101 // BlockChanges.) 102 Unref data.BlockPointer `codec:"u"` 103 Ref data.BlockPointer `codec:"r"` 104 } 105 106 func makeBlockUpdate(unref, ref data.BlockPointer) (blockUpdate, error) { 107 bu := blockUpdate{} 108 err := bu.setUnref(unref) 109 if err != nil { 110 return blockUpdate{}, err 111 } 112 err = bu.setRef(ref) 113 if err != nil { 114 return blockUpdate{}, err 115 } 116 return bu, nil 117 } 118 119 func (u blockUpdate) checkValid() error { 120 if u.Unref == (data.BlockPointer{}) { 121 return errors.New("nil unref") 122 } 123 if u.Ref == (data.BlockPointer{}) { 124 return errors.New("nil ref") 125 } 126 return nil 127 } 128 129 func (u *blockUpdate) setUnref(ptr data.BlockPointer) error { 130 if ptr == (data.BlockPointer{}) { 131 return errors.Errorf("setUnref called with nil ptr") 132 } 133 u.Unref = ptr 134 return nil 135 } 136 137 func (u *blockUpdate) setRef(ptr data.BlockPointer) error { 138 if ptr == (data.BlockPointer{}) { 139 return errors.Errorf("setRef called with nil ptr") 140 } 141 u.Ref = ptr 142 return nil 143 } 144 145 // list codes 146 const ( 147 opsListCode kbfscodec.ExtCode = iota + kbfscodec.ExtCodeListRangeStart 148 ) 149 150 type opsList []op 151 152 // OpCommon are data structures needed by all ops. It is only 153 // exported for serialization purposes. 154 type OpCommon struct { 155 RefBlocks []data.BlockPointer `codec:"r,omitempty"` 156 UnrefBlocks []data.BlockPointer `codec:"u,omitempty"` 157 Updates []blockUpdate `codec:"o,omitempty"` 158 159 codec.UnknownFieldSetHandler 160 161 // writerInfo is the keybase username and device that generated this 162 // operation. 163 // Not exported; only used during conflict resolution. 164 writerInfo writerInfo 165 // finalPath is the final resolved path to the node that this 166 // operation affects in a set of MD updates. Not exported; only 167 // used locally. 168 finalPath data.Path 169 // localTimestamp should be set to the localTimestamp of the 170 // corresponding ImmutableRootMetadata when ops need individual 171 // timestamps. Not exported; only used locally. 172 localTimestamp time.Time 173 } 174 175 func (oc OpCommon) deepCopy() OpCommon { 176 ocCopy := OpCommon{} 177 178 ocCopy.RefBlocks = make([]data.BlockPointer, len(oc.RefBlocks)) 179 copy(ocCopy.RefBlocks, oc.RefBlocks) 180 ocCopy.UnrefBlocks = make([]data.BlockPointer, len(oc.UnrefBlocks)) 181 copy(ocCopy.UnrefBlocks, oc.UnrefBlocks) 182 ocCopy.Updates = make([]blockUpdate, len(oc.Updates)) 183 copy(ocCopy.Updates, oc.Updates) 184 185 // TODO: if we ever need to copy the unknown fields in this 186 // method, we'll have to change the codec interface to make it 187 // possible. 188 189 ocCopy.writerInfo = oc.writerInfo 190 ocCopy.finalPath = oc.finalPath 191 ocCopy.finalPath.Path = make([]data.PathNode, len(oc.finalPath.Path)) 192 copy(ocCopy.finalPath.Path, oc.finalPath.Path) 193 ocCopy.localTimestamp = oc.localTimestamp 194 return ocCopy 195 } 196 197 // AddRefBlock adds this block to the list of newly-referenced blocks 198 // for this op. 199 func (oc *OpCommon) AddRefBlock(ptr data.BlockPointer) { 200 oc.RefBlocks = append(oc.RefBlocks, ptr) 201 } 202 203 // DelRefBlock removes the first reference of the given block from the 204 // list of newly-referenced blocks for this op. 205 func (oc *OpCommon) DelRefBlock(ptr data.BlockPointer) { 206 for i, ref := range oc.RefBlocks { 207 if ptr == ref { 208 oc.RefBlocks = append(oc.RefBlocks[:i], oc.RefBlocks[i+1:]...) 209 break 210 } 211 } 212 } 213 214 // AddUnrefBlock adds this block to the list of newly-unreferenced blocks 215 // for this op. 216 func (oc *OpCommon) AddUnrefBlock(ptr data.BlockPointer) { 217 oc.UnrefBlocks = append(oc.UnrefBlocks, ptr) 218 } 219 220 // DelUnrefBlock removes the first unreference of the given block from 221 // the list of unreferenced blocks for this op. 222 func (oc *OpCommon) DelUnrefBlock(ptr data.BlockPointer) { 223 for i, unref := range oc.UnrefBlocks { 224 if ptr == unref { 225 oc.UnrefBlocks = append(oc.UnrefBlocks[:i], oc.UnrefBlocks[i+1:]...) 226 break 227 } 228 } 229 } 230 231 // AddUpdate adds a mapping from an old block to the new version of 232 // that block, for this op. 233 func (oc *OpCommon) AddUpdate(oldPtr data.BlockPointer, newPtr data.BlockPointer) { 234 // Either pointer may be zero, if we're building an op that 235 // will be fixed up later. 236 bu := blockUpdate{oldPtr, newPtr} 237 oc.Updates = append(oc.Updates, bu) 238 } 239 240 // AddSelfUpdate implements the op interface for OpCommon -- see the 241 // comment in op. 242 func (oc *OpCommon) AddSelfUpdate(ptr data.BlockPointer) { 243 oc.AddUpdate(ptr, ptr) 244 } 245 246 // Refs returns a slice containing all the blocks that were initially 247 // referenced during this op. 248 func (oc *OpCommon) Refs() []data.BlockPointer { 249 return oc.RefBlocks 250 } 251 252 // Unrefs returns a slice containing all the blocks that were 253 // unreferenced during this op. 254 func (oc *OpCommon) Unrefs() []data.BlockPointer { 255 return oc.UnrefBlocks 256 } 257 258 func (oc *OpCommon) setWriterInfo(info writerInfo) { 259 oc.writerInfo = info 260 } 261 262 func (oc *OpCommon) getWriterInfo() writerInfo { 263 return oc.writerInfo 264 } 265 266 func (oc *OpCommon) setFinalPath(p data.Path) { 267 oc.finalPath = p 268 } 269 270 func (oc *OpCommon) getFinalPath() data.Path { 271 return oc.finalPath 272 } 273 274 func (oc *OpCommon) obfuscatedName(name string) data.PathPartString { 275 return data.NewPathPartString(name, oc.finalPath.Obfuscator()) 276 } 277 278 func (oc *OpCommon) setLocalTimestamp(t time.Time) { 279 oc.localTimestamp = t 280 } 281 282 func (oc *OpCommon) getLocalTimestamp() time.Time { 283 return oc.localTimestamp 284 } 285 286 func (oc *OpCommon) checkUpdatesValid() error { 287 for i, update := range oc.Updates { 288 err := update.checkValid() 289 if err != nil { 290 return errors.Errorf( 291 "update[%d]=%v got error: %v", i, update, err) 292 } 293 } 294 return nil 295 } 296 297 func (oc *OpCommon) stringWithRefs(indent string) string { 298 res := "" 299 for i, update := range oc.Updates { 300 res += indent + fmt.Sprintf( 301 "Update[%d]: %v -> %v\n", i, update.Unref, update.Ref) 302 } 303 for i, ref := range oc.RefBlocks { 304 res += indent + fmt.Sprintf("Ref[%d]: %v\n", i, ref) 305 } 306 for i, unref := range oc.UnrefBlocks { 307 res += indent + fmt.Sprintf("Unref[%d]: %v\n", i, unref) 308 } 309 return res 310 } 311 312 // ToEditNotification implements the op interface for OpCommon. 313 func (oc *OpCommon) ToEditNotification( 314 _ kbfsmd.Revision, _ time.Time, _ kbfscrypto.VerifyingKey, 315 _ keybase1.UID, _ tlf.ID) *kbfsedits.NotificationMessage { 316 // Ops embedding this that can be converted should override this. 317 return nil 318 } 319 320 // createOp is an op representing a file or subdirectory creation 321 type createOp struct { 322 OpCommon 323 NewName string `codec:"n"` 324 Dir blockUpdate `codec:"d"` 325 Type data.EntryType `codec:"t"` 326 327 // If true, this create op represents half of a rename operation. 328 // This op should never be persisted. 329 renamed bool 330 331 // If true, during conflict resolution the blocks of the file will 332 // be copied. 333 forceCopy bool 334 335 // If this is set, ths create op needs to be turned has been 336 // turned into a symlink creation locally to avoid a cycle during 337 // conflict resolution, and the following field represents the 338 // text of the symlink. This op should never be persisted. 339 crSymPath string 340 } 341 342 func newCreateOp(name string, oldDir data.BlockPointer, t data.EntryType) (*createOp, error) { 343 co := &createOp{ 344 NewName: name, 345 } 346 err := co.Dir.setUnref(oldDir) 347 if err != nil { 348 return nil, err 349 } 350 co.Type = t 351 return co, nil 352 } 353 354 func (co *createOp) deepCopy() op { 355 coCopy := *co 356 coCopy.OpCommon = co.OpCommon.deepCopy() 357 return &coCopy 358 } 359 360 func newCreateOpForRootDir() *createOp { 361 return &createOp{ 362 Type: data.Dir, 363 } 364 } 365 366 func (co *createOp) AddUpdate(oldPtr data.BlockPointer, newPtr data.BlockPointer) { 367 if co.Dir == (blockUpdate{}) { 368 panic("AddUpdate called on create op with empty Dir " + 369 "(probably create op for root dir)") 370 } 371 if oldPtr == co.Dir.Unref { 372 err := co.Dir.setRef(newPtr) 373 if err != nil { 374 panic(err) 375 } 376 return 377 } 378 co.OpCommon.AddUpdate(oldPtr, newPtr) 379 } 380 381 // AddSelfUpdate implements the op interface for createOp -- see the 382 // comment in op. 383 func (co *createOp) AddSelfUpdate(ptr data.BlockPointer) { 384 co.AddUpdate(ptr, ptr) 385 } 386 387 func (co *createOp) SizeExceptUpdates() uint64 { 388 return uint64(len(co.NewName)) 389 } 390 391 func (co *createOp) allUpdates() []blockUpdate { 392 updates := make([]blockUpdate, len(co.Updates)) 393 copy(updates, co.Updates) 394 return append(updates, co.Dir) 395 } 396 397 func (co *createOp) checkValid() error { 398 if co.NewName == "" { 399 // Must be for root dir. 400 return nil 401 } 402 403 err := co.Dir.checkValid() 404 if err != nil { 405 return errors.Errorf("createOp.Dir=%v got error: %v", co.Dir, err) 406 } 407 return co.checkUpdatesValid() 408 } 409 410 func (co *createOp) obfuscatedNewName() data.PathPartString { 411 return co.obfuscatedName(co.NewName) 412 } 413 414 func (co *createOp) String() string { 415 res := fmt.Sprintf("create %s (%s)", co.obfuscatedNewName(), co.Type) 416 if co.renamed { 417 res += " (renamed)" 418 } 419 return res 420 } 421 422 func (co *createOp) Plaintext() string { 423 res := fmt.Sprintf("create %s (%s)", co.NewName, co.Type) 424 if co.renamed { 425 res += " (renamed)" 426 } 427 return res 428 } 429 430 func (co *createOp) StringWithRefs(indent string) string { 431 res := co.String() + "\n" 432 res += indent + fmt.Sprintf("Dir: %v -> %v\n", co.Dir.Unref, co.Dir.Ref) 433 res += co.stringWithRefs(indent) 434 return res 435 } 436 437 func (co *createOp) checkConflict( 438 ctx context.Context, renamer ConflictRenamer, mergedOp op, 439 isFile bool) (crAction, error) { 440 if realMergedOp, ok := mergedOp.(*createOp); ok { 441 // Conflicts if this creates the same name and one of them 442 // isn't creating a directory. 443 sameName := (realMergedOp.NewName == co.NewName) 444 if sameName && (realMergedOp.Type != data.Dir || co.Type != data.Dir) { 445 if realMergedOp.Type != data.Dir && 446 (co.Type == data.Dir || co.crSymPath != "") { 447 // Rename the merged entry only if the unmerged one is 448 // a directory (or to-be-sympath'd directory) and the 449 // merged one is not. 450 toName, err := renamer.ConflictRename( 451 ctx, mergedOp, co.NewName) 452 if err != nil { 453 return nil, err 454 } 455 return &renameMergedAction{ 456 fromName: co.obfuscatedNewName(), 457 toName: co.obfuscatedName(toName), 458 symPath: co.obfuscatedName(co.crSymPath), 459 }, nil 460 } 461 // Otherwise rename the unmerged entry (guaranteed to be a file). 462 toName, err := renamer.ConflictRename( 463 ctx, co, co.NewName) 464 if err != nil { 465 return nil, err 466 } 467 return &renameUnmergedAction{ 468 fromName: co.obfuscatedNewName(), 469 toName: co.obfuscatedName(toName), 470 symPath: co.obfuscatedName(co.crSymPath), 471 }, nil 472 } 473 474 // If they are both directories, and one of them is a rename, 475 // then we have a conflict and need to rename the renamed one. 476 // 477 // TODO: Implement a better merging strategy for when an 478 // existing directory gets into a rename conflict with another 479 // existing or new directory. 480 if sameName && realMergedOp.Type == data.Dir && co.Type == data.Dir && 481 (realMergedOp.renamed || co.renamed) { 482 // Always rename the unmerged one 483 toName, err := renamer.ConflictRename( 484 ctx, co, co.NewName) 485 if err != nil { 486 return nil, err 487 } 488 return ©UnmergedEntryAction{ 489 fromName: co.obfuscatedNewName(), 490 toName: co.obfuscatedName(toName), 491 symPath: co.obfuscatedName(co.crSymPath), 492 unique: true, 493 }, nil 494 } 495 } 496 // Doesn't conflict with any rmOps, because the default action 497 // will just re-create it in the merged branch as necessary. 498 return nil, nil 499 } 500 501 func (co *createOp) getDefaultAction(mergedPath data.Path) crAction { 502 newName := co.obfuscatedNewName() 503 if co.forceCopy { 504 return &renameUnmergedAction{ 505 fromName: newName, 506 toName: newName, 507 symPath: co.obfuscatedName(co.crSymPath), 508 } 509 } 510 return ©UnmergedEntryAction{ 511 fromName: newName, 512 toName: newName, 513 symPath: co.obfuscatedName(co.crSymPath), 514 } 515 } 516 517 func makeBaseEditNotification( 518 rev kbfsmd.Revision, revTime time.Time, device kbfscrypto.VerifyingKey, 519 uid keybase1.UID, tlfID tlf.ID, 520 et data.EntryType) kbfsedits.NotificationMessage { 521 var t kbfsedits.EntryType 522 switch et { 523 case data.File, data.Exec: 524 t = kbfsedits.EntryTypeFile 525 case data.Dir: 526 t = kbfsedits.EntryTypeDir 527 case data.Sym: 528 t = kbfsedits.EntryTypeSym 529 } 530 return kbfsedits.NotificationMessage{ 531 Version: kbfsedits.NotificationV2, 532 FileType: t, 533 Time: revTime, 534 Revision: rev, 535 Device: device, 536 UID: uid, 537 FolderID: tlfID, 538 } 539 } 540 541 func (co *createOp) ToEditNotification( 542 rev kbfsmd.Revision, revTime time.Time, device kbfscrypto.VerifyingKey, 543 uid keybase1.UID, tlfID tlf.ID) *kbfsedits.NotificationMessage { 544 n := makeBaseEditNotification(rev, revTime, device, uid, tlfID, co.Type) 545 n.Filename = co.getFinalPath().ChildPathNoPtr(co.obfuscatedNewName(), nil). 546 CanonicalPathPlaintext() 547 n.Type = kbfsedits.NotificationCreate 548 return &n 549 } 550 551 // rmOp is an op representing a file or subdirectory removal 552 type rmOp struct { 553 OpCommon 554 OldName string `codec:"n"` 555 Dir blockUpdate `codec:"d"` 556 RemovedType data.EntryType `codec:"rt"` 557 558 // Indicates that the resolution process should skip this rm op. 559 // Likely indicates the rm half of a cycle-creating rename. 560 dropThis bool 561 } 562 563 func newRmOp(name string, oldDir data.BlockPointer, removedType data.EntryType) ( 564 *rmOp, error) { 565 ro := &rmOp{ 566 OldName: name, 567 RemovedType: removedType, 568 } 569 err := ro.Dir.setUnref(oldDir) 570 if err != nil { 571 return nil, err 572 } 573 return ro, nil 574 } 575 576 func (ro *rmOp) deepCopy() op { 577 roCopy := *ro 578 roCopy.OpCommon = ro.OpCommon.deepCopy() 579 return &roCopy 580 } 581 582 func (ro *rmOp) AddUpdate(oldPtr data.BlockPointer, newPtr data.BlockPointer) { 583 if oldPtr == ro.Dir.Unref { 584 err := ro.Dir.setRef(newPtr) 585 if err != nil { 586 panic(err) 587 } 588 return 589 } 590 ro.OpCommon.AddUpdate(oldPtr, newPtr) 591 } 592 593 // AddSelfUpdate implements the op interface for rmOp -- see the 594 // comment in op. 595 func (ro *rmOp) AddSelfUpdate(ptr data.BlockPointer) { 596 ro.AddUpdate(ptr, ptr) 597 } 598 599 func (ro *rmOp) SizeExceptUpdates() uint64 { 600 return uint64(len(ro.OldName)) 601 } 602 603 func (ro *rmOp) allUpdates() []blockUpdate { 604 updates := make([]blockUpdate, len(ro.Updates)) 605 copy(updates, ro.Updates) 606 return append(updates, ro.Dir) 607 } 608 609 func (ro *rmOp) checkValid() error { 610 err := ro.Dir.checkValid() 611 if err != nil { 612 return errors.Errorf("rmOp.Dir=%v got error: %v", ro.Dir, err) 613 } 614 return ro.checkUpdatesValid() 615 } 616 617 func (ro *rmOp) obfuscatedOldName() data.PathPartString { 618 return ro.obfuscatedName(ro.OldName) 619 } 620 621 func (ro *rmOp) String() string { 622 return fmt.Sprintf("rm %s", ro.obfuscatedOldName()) 623 } 624 625 func (ro *rmOp) Plaintext() string { 626 return fmt.Sprintf("rm %s", ro.OldName) 627 } 628 629 func (ro *rmOp) StringWithRefs(indent string) string { 630 res := ro.String() + "\n" 631 res += indent + fmt.Sprintf("Dir: %v -> %v\n", ro.Dir.Unref, ro.Dir.Ref) 632 res += ro.stringWithRefs(indent) 633 return res 634 } 635 636 func (ro *rmOp) checkConflict( 637 ctx context.Context, renamer ConflictRenamer, mergedOp op, 638 isFile bool) (crAction, error) { 639 switch realMergedOp := mergedOp.(type) { 640 case *createOp: 641 if realMergedOp.NewName == ro.OldName { 642 // Conflicts if this creates the same name. This can only 643 // happen if the merged branch deleted the old node and 644 // re-created it, in which case it is totally fine to drop 645 // this rm op for the original node. 646 return &dropUnmergedAction{op: ro}, nil 647 } 648 case *rmOp: 649 if realMergedOp.OldName == ro.OldName { 650 // Both removed the same file. 651 return &dropUnmergedAction{op: ro}, nil 652 } 653 } 654 return nil, nil 655 } 656 657 func (ro *rmOp) getDefaultAction(mergedPath data.Path) crAction { 658 if ro.dropThis { 659 return &dropUnmergedAction{op: ro} 660 } 661 return &rmMergedEntryAction{name: ro.obfuscatedOldName()} 662 } 663 664 func (ro *rmOp) ToEditNotification( 665 rev kbfsmd.Revision, revTime time.Time, device kbfscrypto.VerifyingKey, 666 uid keybase1.UID, tlfID tlf.ID) *kbfsedits.NotificationMessage { 667 n := makeBaseEditNotification( 668 rev, revTime, device, uid, tlfID, ro.RemovedType) 669 n.Filename = ro.getFinalPath().ChildPathNoPtr(ro.obfuscatedOldName(), nil). 670 CanonicalPathPlaintext() 671 n.Type = kbfsedits.NotificationDelete 672 return &n 673 } 674 675 // renameOp is an op representing a rename of a file/subdirectory from 676 // one directory to another. If this is a rename within the same 677 // directory, NewDir will be equivalent to blockUpdate{}. renameOp 678 // records the moved pointer, even though it doesn't change as part of 679 // the operation, to make it possible to track the full path of 680 // directories for the purposes of conflict resolution. 681 type renameOp struct { 682 OpCommon 683 OldName string `codec:"on"` 684 OldDir blockUpdate `codec:"od"` 685 NewName string `codec:"nn"` 686 NewDir blockUpdate `codec:"nd"` 687 Renamed data.BlockPointer `codec:"re"` 688 RenamedType data.EntryType `codec:"rt"` 689 690 // oldFinalPath is the final resolved path to the old directory 691 // containing the renamed node. Not exported; only used locally. 692 oldFinalPath data.Path 693 } 694 695 func newRenameOp(oldName string, oldOldDir data.BlockPointer, 696 newName string, oldNewDir data.BlockPointer, renamed data.BlockPointer, 697 renamedType data.EntryType) (*renameOp, error) { 698 ro := &renameOp{ 699 OldName: oldName, 700 NewName: newName, 701 Renamed: renamed, 702 RenamedType: renamedType, 703 } 704 err := ro.OldDir.setUnref(oldOldDir) 705 if err != nil { 706 return nil, err 707 } 708 // If we are renaming within a directory, let the NewDir remain empty. 709 if oldOldDir != oldNewDir { 710 err := ro.NewDir.setUnref(oldNewDir) 711 if err != nil { 712 return nil, err 713 } 714 } 715 return ro, nil 716 } 717 718 func (ro *renameOp) deepCopy() op { 719 roCopy := *ro 720 roCopy.OpCommon = ro.OpCommon.deepCopy() 721 return &roCopy 722 } 723 724 func (ro *renameOp) AddUpdate(oldPtr data.BlockPointer, newPtr data.BlockPointer) { 725 if oldPtr == ro.OldDir.Unref { 726 err := ro.OldDir.setRef(newPtr) 727 if err != nil { 728 panic(err) 729 } 730 return 731 } 732 if ro.NewDir != (blockUpdate{}) && oldPtr == ro.NewDir.Unref { 733 err := ro.NewDir.setRef(newPtr) 734 if err != nil { 735 panic(err) 736 } 737 return 738 } 739 ro.OpCommon.AddUpdate(oldPtr, newPtr) 740 } 741 742 // AddSelfUpdate implements the op interface for renameOp -- see the 743 // comment in op. 744 func (ro *renameOp) AddSelfUpdate(ptr data.BlockPointer) { 745 ro.AddUpdate(ptr, ptr) 746 } 747 748 func (ro *renameOp) SizeExceptUpdates() uint64 { 749 return uint64(len(ro.NewName) + len(ro.NewName)) 750 } 751 752 func (ro *renameOp) allUpdates() []blockUpdate { 753 updates := make([]blockUpdate, len(ro.Updates)) 754 copy(updates, ro.Updates) 755 if ro.NewDir != (blockUpdate{}) { 756 return append(updates, ro.NewDir, ro.OldDir) 757 } 758 return append(updates, ro.OldDir) 759 } 760 761 func (ro *renameOp) checkValid() error { 762 err := ro.OldDir.checkValid() 763 if err != nil { 764 return errors.Errorf("renameOp.OldDir=%v got error: %v", 765 ro.OldDir, err) 766 } 767 if ro.NewDir != (blockUpdate{}) { 768 err = ro.NewDir.checkValid() 769 if err != nil { 770 return errors.Errorf("renameOp.NewDir=%v got error: %v", 771 ro.NewDir, err) 772 } 773 } 774 775 return ro.checkUpdatesValid() 776 } 777 778 func (ro *renameOp) obfuscatedOldName() data.PathPartString { 779 return ro.obfuscatedName(ro.OldName) 780 } 781 782 func (ro *renameOp) obfuscatedNewName() data.PathPartString { 783 return ro.obfuscatedName(ro.NewName) 784 } 785 786 func (ro *renameOp) String() string { 787 return fmt.Sprintf("rename %s -> %s (%s)", 788 ro.obfuscatedOldName(), ro.obfuscatedNewName(), ro.RenamedType) 789 } 790 791 func (ro *renameOp) Plaintext() string { 792 return fmt.Sprintf("rename %s -> %s (%s)", 793 ro.OldName, ro.NewName, ro.RenamedType) 794 } 795 796 func (ro *renameOp) StringWithRefs(indent string) string { 797 res := ro.String() + "\n" 798 res += indent + fmt.Sprintf("OldDir: %v -> %v\n", 799 ro.OldDir.Unref, ro.OldDir.Ref) 800 if ro.NewDir != (blockUpdate{}) { 801 res += indent + fmt.Sprintf("NewDir: %v -> %v\n", 802 ro.NewDir.Unref, ro.NewDir.Ref) 803 } else { 804 res += indent + "NewDir: same as above\n" 805 } 806 res += indent + fmt.Sprintf("Renamed: %v\n", ro.Renamed) 807 res += ro.stringWithRefs(indent) 808 return res 809 } 810 811 func (ro *renameOp) checkConflict( 812 ctx context.Context, renamer ConflictRenamer, mergedOp op, 813 isFile bool) (crAction, error) { 814 return nil, errors.Errorf("Unexpected conflict check on a rename op: %s", ro) 815 } 816 817 func (ro *renameOp) getDefaultAction(mergedPath data.Path) crAction { 818 return nil 819 } 820 821 func (ro *renameOp) ToEditNotification( 822 rev kbfsmd.Revision, revTime time.Time, device kbfscrypto.VerifyingKey, 823 uid keybase1.UID, tlfID tlf.ID) *kbfsedits.NotificationMessage { 824 n := makeBaseEditNotification( 825 rev, revTime, device, uid, tlfID, ro.RenamedType) 826 n.Filename = ro.getFinalPath().ChildPathNoPtr(ro.obfuscatedNewName(), nil). 827 CanonicalPathPlaintext() 828 n.Type = kbfsedits.NotificationRename 829 n.Params = &kbfsedits.NotificationParams{ 830 OldFilename: ro.oldFinalPath.ChildPathNoPtr( 831 ro.obfuscatedOldName(), nil).CanonicalPathPlaintext(), 832 } 833 return &n 834 } 835 836 // WriteRange represents a file modification. Len is 0 for a 837 // truncate. 838 type WriteRange struct { 839 Off uint64 `codec:"o"` 840 Len uint64 `codec:"l,omitempty"` // 0 for truncates 841 842 codec.UnknownFieldSetHandler 843 } 844 845 func (w WriteRange) isTruncate() bool { 846 return w.Len == 0 847 } 848 849 // End returns the index of the largest byte not affected by this 850 // write. It only makes sense to call this for non-truncates. 851 func (w WriteRange) End() uint64 { 852 if w.isTruncate() { 853 panic("Truncates don't have an end") 854 } 855 return w.Off + w.Len 856 } 857 858 // Affects returns true if the regions affected by this write 859 // operation and `other` overlap in some way. Specifically, it 860 // returns true if: 861 // 862 // - both operations are writes and their write ranges overlap; 863 // - one operation is a write and one is a truncate, and the truncate is 864 // within the write's range or before it; or 865 // - both operations are truncates. 866 func (w WriteRange) Affects(other WriteRange) bool { 867 if w.isTruncate() { 868 if other.isTruncate() { 869 return true 870 } 871 // A truncate affects a write if it lands inside or before the 872 // write. 873 return other.End() > w.Off 874 } else if other.isTruncate() { 875 return w.End() > other.Off 876 } 877 // Both are writes -- do their ranges overlap? 878 return (w.Off <= other.End() && other.End() <= w.End()) || 879 (other.Off <= w.End() && w.End() <= other.End()) 880 } 881 882 // syncOp is an op that represents a series of writes to a file. 883 type syncOp struct { 884 OpCommon 885 File blockUpdate `codec:"f"` 886 Writes []WriteRange `codec:"w"` 887 888 // If true, this says that if there is a conflict involving this 889 // op, we should keep the unmerged name rather than construct a 890 // conflict name (probably because the new name already 891 // diverges from the name in the other branch). 892 keepUnmergedTailName bool 893 } 894 895 func newSyncOp(oldFile data.BlockPointer) (*syncOp, error) { 896 so := &syncOp{} 897 err := so.File.setUnref(oldFile) 898 if err != nil { 899 return nil, err 900 } 901 so.resetUpdateState() 902 return so, nil 903 } 904 905 func (so *syncOp) deepCopy() op { 906 soCopy := *so 907 soCopy.OpCommon = so.OpCommon.deepCopy() 908 soCopy.Writes = make([]WriteRange, len(so.Writes)) 909 copy(soCopy.Writes, so.Writes) 910 return &soCopy 911 } 912 913 func (so *syncOp) resetUpdateState() { 914 so.Updates = nil 915 } 916 917 func (so *syncOp) AddUpdate(oldPtr data.BlockPointer, newPtr data.BlockPointer) { 918 if oldPtr == so.File.Unref { 919 err := so.File.setRef(newPtr) 920 if err != nil { 921 panic(err) 922 } 923 return 924 } 925 so.OpCommon.AddUpdate(oldPtr, newPtr) 926 } 927 928 // AddSelfUpdate implements the op interface for syncOp -- see the 929 // comment in op. 930 func (so *syncOp) AddSelfUpdate(ptr data.BlockPointer) { 931 so.AddUpdate(ptr, ptr) 932 } 933 934 func (so *syncOp) addWrite(off uint64, length uint64) WriteRange { 935 latestWrite := WriteRange{Off: off, Len: length} 936 so.Writes = append(so.Writes, latestWrite) 937 return latestWrite 938 } 939 940 func (so *syncOp) addTruncate(off uint64) WriteRange { 941 latestWrite := WriteRange{Off: off, Len: 0} 942 so.Writes = append(so.Writes, latestWrite) 943 return latestWrite 944 } 945 946 func (so *syncOp) SizeExceptUpdates() uint64 { 947 return uint64(len(so.Writes) * 16) 948 } 949 950 func (so *syncOp) allUpdates() []blockUpdate { 951 updates := make([]blockUpdate, len(so.Updates)) 952 copy(updates, so.Updates) 953 return append(updates, so.File) 954 } 955 956 func (so *syncOp) checkValid() error { 957 err := so.File.checkValid() 958 if err != nil { 959 return errors.Errorf("syncOp.File=%v got error: %v", so.File, err) 960 } 961 return so.checkUpdatesValid() 962 } 963 964 func (so *syncOp) String() string { 965 var writes []string 966 for _, r := range so.Writes { 967 writes = append(writes, fmt.Sprintf("{off=%d, len=%d}", r.Off, r.Len)) 968 } 969 return fmt.Sprintf("sync [%s]", strings.Join(writes, ", ")) 970 } 971 972 func (so *syncOp) Plaintext() string { 973 return so.String() 974 } 975 976 func (so *syncOp) StringWithRefs(indent string) string { 977 res := so.String() + "\n" 978 res += indent + fmt.Sprintf("File: %v -> %v\n", so.File.Unref, so.File.Ref) 979 res += so.stringWithRefs(indent) 980 return res 981 } 982 983 func (so *syncOp) checkConflict( 984 ctx context.Context, renamer ConflictRenamer, mergedOp op, 985 isFile bool) (crAction, error) { 986 switch mergedOp.(type) { 987 case *syncOp: 988 // Any sync on the same file is a conflict. (TODO: add 989 // type-specific intelligent conflict resolvers for file 990 // contents?) 991 toName, err := renamer.ConflictRename( 992 ctx, so, mergedOp.getFinalPath().TailName().Plaintext()) 993 if err != nil { 994 return nil, err 995 } 996 997 if so.keepUnmergedTailName { 998 toName = so.getFinalPath().TailName().Plaintext() 999 } 1000 1001 return &renameUnmergedAction{ 1002 fromName: so.getFinalPath().TailName(), 1003 toName: so.obfuscatedName(toName), 1004 unmergedParentMostRecent: so.getFinalPath().ParentPath().TailPointer(), 1005 mergedParentMostRecent: mergedOp.getFinalPath().ParentPath(). 1006 TailPointer(), 1007 }, nil 1008 case *setAttrOp: 1009 // Someone on the merged path explicitly set an attribute, so 1010 // just copy the size and blockpointer over. 1011 return ©UnmergedAttrAction{ 1012 fromName: so.getFinalPath().TailName(), 1013 toName: mergedOp.getFinalPath().TailName(), 1014 attr: []attrChange{sizeAttr}, 1015 }, nil 1016 } 1017 return nil, nil 1018 } 1019 1020 func (so *syncOp) getDefaultAction(mergedPath data.Path) crAction { 1021 return ©UnmergedEntryAction{ 1022 fromName: so.getFinalPath().TailName(), 1023 toName: mergedPath.TailName(), 1024 symPath: so.obfuscatedName(""), 1025 } 1026 } 1027 1028 func (so *syncOp) ToEditNotification( 1029 rev kbfsmd.Revision, revTime time.Time, device kbfscrypto.VerifyingKey, 1030 uid keybase1.UID, tlfID tlf.ID) *kbfsedits.NotificationMessage { 1031 n := makeBaseEditNotification(rev, revTime, device, uid, tlfID, data.File) 1032 n.Filename = so.getFinalPath().CanonicalPathPlaintext() 1033 n.Type = kbfsedits.NotificationModify 1034 var mods []kbfsedits.ModifyRange 1035 for _, w := range so.Writes { 1036 mods = append(mods, kbfsedits.ModifyRange{ 1037 Offset: w.Off, 1038 Length: w.Len, 1039 }) 1040 } 1041 n.Params = &kbfsedits.NotificationParams{ 1042 Modifies: mods, 1043 } 1044 return &n 1045 } 1046 1047 // In the functions below. a collapsed []WriteRange is a sequence of 1048 // non-overlapping writes with strictly increasing Off, and maybe a 1049 // trailing truncate (with strictly greater Off). 1050 1051 // coalesceWrites combines the given `wNew` with the head and tail of 1052 // the given collapsed `existingWrites` slice. For example, if the 1053 // new write is {5, 100}, and `existingWrites` = [{7,5}, {18,10}, 1054 // {98,10}], the returned write will be {5,103}. There may be a 1055 // truncate at the end of the returned slice as well. 1056 func coalesceWrites(existingWrites []WriteRange, wNew WriteRange) []WriteRange { 1057 if wNew.isTruncate() { 1058 panic("coalesceWrites cannot be called with a new truncate.") 1059 } 1060 if len(existingWrites) == 0 { 1061 return []WriteRange{wNew} 1062 } 1063 newOff := wNew.Off 1064 newEnd := wNew.End() 1065 wOldHead := existingWrites[0] 1066 wOldTail := existingWrites[len(existingWrites)-1] 1067 if !wOldTail.isTruncate() && wOldTail.End() > newEnd { 1068 newEnd = wOldTail.End() 1069 } 1070 if !wOldHead.isTruncate() && wOldHead.Off < newOff { 1071 newOff = wOldHead.Off 1072 } 1073 ret := []WriteRange{{Off: newOff, Len: newEnd - newOff}} 1074 if wOldTail.isTruncate() { 1075 ret = append(ret, WriteRange{Off: newEnd}) 1076 } 1077 return ret 1078 } 1079 1080 // Assumes writes is already collapsed, i.e. a sequence of 1081 // non-overlapping writes with strictly increasing Off, and maybe a 1082 // trailing truncate (with strictly greater Off). 1083 func addToCollapsedWriteRange(writes []WriteRange, 1084 wNew WriteRange) []WriteRange { 1085 // Form three regions: head, mid, and tail: head is the maximal prefix 1086 // of writes less than (with respect to Off) and unaffected by wNew, 1087 // tail is the maximal suffix of writes greater than (with respect to 1088 // Off) and unaffected by wNew, and mid is everything else, i.e. the 1089 // range of writes affected by wNew. 1090 var headEnd int 1091 for ; headEnd < len(writes); headEnd++ { 1092 wOld := writes[headEnd] 1093 if wOld.Off >= wNew.Off || wNew.Affects(wOld) { 1094 break 1095 } 1096 } 1097 head := writes[:headEnd] 1098 1099 if wNew.isTruncate() { 1100 // end is empty, since a truncate affects a suffix of writes. 1101 mid := writes[headEnd:] 1102 1103 switch { 1104 case len(mid) == 0: 1105 // Truncate past the last write. 1106 return append(head, wNew) 1107 case mid[0].isTruncate(): 1108 if mid[0].Off < wNew.Off { 1109 // A larger new truncate causes zero-fill. 1110 zeroLen := wNew.Off - mid[0].Off 1111 if len(head) > 0 { 1112 lastHead := head[len(head)-1] 1113 if lastHead.Off+lastHead.Len == mid[0].Off { 1114 // Combine this zero-fill with the previous write. 1115 head[len(head)-1].Len += zeroLen 1116 return append(head, wNew) 1117 } 1118 } 1119 return append(head, 1120 WriteRange{Off: mid[0].Off, Len: zeroLen}, wNew) 1121 } 1122 return append(head, wNew) 1123 case mid[0].Off < wNew.Off: 1124 return append(head, WriteRange{ 1125 Off: mid[0].Off, 1126 Len: wNew.Off - mid[0].Off, 1127 }, wNew) 1128 } 1129 return append(head, wNew) 1130 } 1131 1132 // wNew is a write. 1133 1134 midEnd := headEnd 1135 for ; midEnd < len(writes); midEnd++ { 1136 wOld := writes[midEnd] 1137 if !wNew.Affects(wOld) { 1138 break 1139 } 1140 } 1141 1142 mid := writes[headEnd:midEnd] 1143 end := writes[midEnd:] 1144 mid = coalesceWrites(mid, wNew) 1145 return append(head, append(mid, end...)...) 1146 } 1147 1148 // collapseWriteRange returns a set of writes that represent the final 1149 // dirty state of this file after this syncOp, given a previous write 1150 // range. It coalesces overlapping dirty writes, and it erases any 1151 // writes that occurred before a truncation with an offset smaller 1152 // than its max dirty byte. 1153 // 1154 // This function assumes that `writes` has already been collapsed (or 1155 // is nil). 1156 // 1157 // NOTE: Truncates past a file's end get turned into writes by 1158 // folderBranchOps, but in the future we may have bona fide truncate 1159 // WriteRanges past a file's end. 1160 func (so *syncOp) collapseWriteRange(writes []WriteRange) ( 1161 newWrites []WriteRange) { 1162 newWrites = writes 1163 for _, wNew := range so.Writes { 1164 newWrites = addToCollapsedWriteRange(newWrites, wNew) 1165 } 1166 return newWrites 1167 } 1168 1169 type attrChange uint16 1170 1171 const ( 1172 exAttr attrChange = iota 1173 mtimeAttr 1174 sizeAttr // only used during conflict resolution 1175 ) 1176 1177 func (ac attrChange) String() string { 1178 switch ac { 1179 case exAttr: 1180 return "ex" 1181 case mtimeAttr: 1182 return "mtime" 1183 case sizeAttr: 1184 return "size" 1185 } 1186 return "<invalid attrChange>" 1187 } 1188 1189 // setAttrOp is an op that represents changing the attributes of a 1190 // file/subdirectory with in a directory. 1191 type setAttrOp struct { 1192 OpCommon 1193 Name string `codec:"n"` 1194 Dir blockUpdate `codec:"d"` 1195 Attr attrChange `codec:"a"` 1196 File data.BlockPointer `codec:"f"` 1197 1198 // If true, this says that if there is a conflict involving this 1199 // op, we should keep the unmerged name rather than construct a 1200 // conflict name (probably because the new name already 1201 // diverges from the name in the other branch). 1202 keepUnmergedTailName bool 1203 } 1204 1205 func newSetAttrOp(name string, oldDir data.BlockPointer, 1206 attr attrChange, file data.BlockPointer) (*setAttrOp, error) { 1207 sao := &setAttrOp{ 1208 Name: name, 1209 } 1210 err := sao.Dir.setUnref(oldDir) 1211 if err != nil { 1212 return nil, err 1213 } 1214 sao.Attr = attr 1215 sao.File = file 1216 return sao, nil 1217 } 1218 1219 func (sao *setAttrOp) deepCopy() op { 1220 saoCopy := *sao 1221 saoCopy.OpCommon = sao.OpCommon.deepCopy() 1222 return &saoCopy 1223 } 1224 1225 func (sao *setAttrOp) AddUpdate(oldPtr data.BlockPointer, newPtr data.BlockPointer) { 1226 if oldPtr == sao.Dir.Unref { 1227 err := sao.Dir.setRef(newPtr) 1228 if err != nil { 1229 panic(err) 1230 } 1231 return 1232 } 1233 sao.OpCommon.AddUpdate(oldPtr, newPtr) 1234 } 1235 1236 // AddSelfUpdate implements the op interface for setAttrOp -- see the 1237 // comment in op. 1238 func (sao *setAttrOp) AddSelfUpdate(ptr data.BlockPointer) { 1239 sao.AddUpdate(ptr, ptr) 1240 } 1241 1242 func (sao *setAttrOp) SizeExceptUpdates() uint64 { 1243 return uint64(len(sao.Name)) 1244 } 1245 1246 func (sao *setAttrOp) allUpdates() []blockUpdate { 1247 updates := make([]blockUpdate, len(sao.Updates)) 1248 copy(updates, sao.Updates) 1249 return append(updates, sao.Dir) 1250 } 1251 1252 func (sao *setAttrOp) checkValid() error { 1253 err := sao.Dir.checkValid() 1254 if err != nil { 1255 return errors.Errorf("setAttrOp.Dir=%v got error: %v", sao.Dir, err) 1256 } 1257 return sao.checkUpdatesValid() 1258 } 1259 1260 func (sao *setAttrOp) obfuscatedEntryName() data.PathPartString { 1261 return data.NewPathPartString( 1262 sao.Name, sao.finalPath.ParentPath().Obfuscator()) 1263 } 1264 1265 func (sao *setAttrOp) String() string { 1266 return fmt.Sprintf("setAttr %s (%s)", sao.obfuscatedEntryName(), sao.Attr) 1267 } 1268 1269 func (sao *setAttrOp) Plaintext() string { 1270 return fmt.Sprintf("setAttr %s (%s)", sao.Name, sao.Attr) 1271 } 1272 1273 func (sao *setAttrOp) StringWithRefs(indent string) string { 1274 res := sao.String() + "\n" 1275 res += indent + fmt.Sprintf("Dir: %v -> %v\n", sao.Dir.Unref, sao.Dir.Ref) 1276 res += indent + fmt.Sprintf("File: %v\n", sao.File) 1277 res += sao.stringWithRefs(indent) 1278 return res 1279 } 1280 1281 func (sao *setAttrOp) checkConflict( 1282 ctx context.Context, renamer ConflictRenamer, mergedOp op, 1283 isFile bool) (crAction, error) { 1284 if realMergedOp, ok := mergedOp.(*setAttrOp); ok && 1285 realMergedOp.Attr == sao.Attr { 1286 var symPath string 1287 var causedByAttr attrChange 1288 if !isFile { 1289 // A directory has a conflict on an mtime attribute. 1290 // Create a symlink entry with the unmerged mtime 1291 // pointing to the merged entry. 1292 symPath = mergedOp.getFinalPath().TailName().Plaintext() 1293 causedByAttr = sao.Attr 1294 } 1295 1296 // A set attr for the same attribute on the same file is a 1297 // conflict. 1298 fromName := sao.getFinalPath().TailName() 1299 toName, err := renamer.ConflictRename( 1300 ctx, sao, fromName.Plaintext()) 1301 if err != nil { 1302 return nil, err 1303 } 1304 1305 if sao.keepUnmergedTailName { 1306 toName = sao.getFinalPath().TailName().Plaintext() 1307 } 1308 1309 toNamePPS := data.NewPathPartString( 1310 toName, sao.finalPath.ParentPath().Obfuscator()) 1311 return &renameUnmergedAction{ 1312 fromName: fromName, 1313 toName: toNamePPS, 1314 symPath: sao.obfuscatedName(symPath), 1315 causedByAttr: causedByAttr, 1316 unmergedParentMostRecent: sao.getFinalPath().ParentPath().TailPointer(), 1317 mergedParentMostRecent: mergedOp.getFinalPath().ParentPath(). 1318 TailPointer(), 1319 }, nil 1320 } 1321 return nil, nil 1322 } 1323 1324 func (sao *setAttrOp) getDefaultAction(mergedPath data.Path) crAction { 1325 return ©UnmergedAttrAction{ 1326 fromName: sao.getFinalPath().TailName(), 1327 toName: mergedPath.TailName(), 1328 attr: []attrChange{sao.Attr}, 1329 } 1330 } 1331 1332 // resolutionOp is an op that represents the block changes that took 1333 // place as part of a conflict resolution. 1334 type resolutionOp struct { 1335 OpCommon 1336 UncommittedUnrefs []data.BlockPointer `codec:"uu"` 1337 } 1338 1339 func newResolutionOp() *resolutionOp { 1340 ro := &resolutionOp{} 1341 return ro 1342 } 1343 1344 func (ro *resolutionOp) deepCopy() op { 1345 roCopy := *ro 1346 roCopy.OpCommon = ro.OpCommon.deepCopy() 1347 roCopy.UncommittedUnrefs = make([]data.BlockPointer, len(ro.UncommittedUnrefs)) 1348 copy(roCopy.UncommittedUnrefs, ro.UncommittedUnrefs) 1349 return &roCopy 1350 } 1351 1352 func (ro *resolutionOp) Unrefs() []data.BlockPointer { 1353 return append(ro.OpCommon.Unrefs(), ro.UncommittedUnrefs...) 1354 } 1355 1356 func (ro *resolutionOp) DelUnrefBlock(ptr data.BlockPointer) { 1357 ro.OpCommon.DelUnrefBlock(ptr) 1358 for i, unref := range ro.UncommittedUnrefs { 1359 if ptr == unref { 1360 ro.UncommittedUnrefs = append( 1361 ro.UncommittedUnrefs[:i], ro.UncommittedUnrefs[i+1:]...) 1362 break 1363 } 1364 } 1365 } 1366 1367 func (ro *resolutionOp) SizeExceptUpdates() uint64 { 1368 return 0 1369 } 1370 1371 func (ro *resolutionOp) allUpdates() []blockUpdate { 1372 return ro.Updates 1373 } 1374 1375 func (ro *resolutionOp) checkValid() error { 1376 return ro.checkUpdatesValid() 1377 } 1378 1379 func (ro *resolutionOp) String() string { 1380 return "resolution" 1381 } 1382 1383 func (ro *resolutionOp) Plaintext() string { 1384 return ro.String() 1385 } 1386 1387 func (ro *resolutionOp) StringWithRefs(indent string) string { 1388 res := ro.String() + "\n" 1389 res += ro.stringWithRefs(indent) 1390 return res 1391 } 1392 1393 // AddUncommittedUnrefBlock adds this block to the list of blocks that should be 1394 // archived/deleted from the server, but which were never actually 1395 // committed successfully in an MD. Therefore, their sizes don't have 1396 // to be accounted for in any MD size accounting for the TLF. 1397 func (ro *resolutionOp) AddUncommittedUnrefBlock(ptr data.BlockPointer) { 1398 ro.UncommittedUnrefs = append(ro.UncommittedUnrefs, ptr) 1399 } 1400 1401 func (ro *resolutionOp) CommittedUnrefs() []data.BlockPointer { 1402 return ro.OpCommon.Unrefs() 1403 } 1404 1405 func (ro *resolutionOp) checkConflict( 1406 ctx context.Context, renamer ConflictRenamer, mergedOp op, 1407 isFile bool) (crAction, error) { 1408 return nil, nil 1409 } 1410 1411 func (ro *resolutionOp) getDefaultAction(mergedPath data.Path) crAction { 1412 return nil 1413 } 1414 1415 // rekeyOp is an op that represents a rekey on a TLF. 1416 type rekeyOp struct { 1417 OpCommon 1418 } 1419 1420 func newRekeyOp() *rekeyOp { 1421 ro := &rekeyOp{} 1422 return ro 1423 } 1424 1425 func (ro *rekeyOp) deepCopy() op { 1426 roCopy := *ro 1427 roCopy.OpCommon = ro.OpCommon.deepCopy() 1428 return &roCopy 1429 } 1430 1431 func (ro *rekeyOp) SizeExceptUpdates() uint64 { 1432 return 0 1433 } 1434 1435 func (ro *rekeyOp) allUpdates() []blockUpdate { 1436 return ro.Updates 1437 } 1438 1439 func (ro *rekeyOp) checkValid() error { 1440 return ro.checkUpdatesValid() 1441 } 1442 1443 func (ro *rekeyOp) String() string { 1444 return "rekey" 1445 } 1446 1447 func (ro *rekeyOp) Plaintext() string { 1448 return ro.String() 1449 } 1450 1451 func (ro *rekeyOp) StringWithRefs(indent string) string { 1452 res := ro.String() + "\n" 1453 res += ro.stringWithRefs(indent) 1454 return res 1455 } 1456 1457 func (ro *rekeyOp) checkConflict( 1458 ctx context.Context, renamer ConflictRenamer, mergedOp op, 1459 isFile bool) (crAction, error) { 1460 return nil, nil 1461 } 1462 1463 func (ro *rekeyOp) getDefaultAction(mergedPath data.Path) crAction { 1464 return nil 1465 } 1466 1467 // GCOp is an op that represents garbage-collecting the history of a 1468 // folder (which may involve unreferencing blocks that previously held 1469 // operation lists. It may contain unref blocks before it is added to 1470 // the metadata ops list. 1471 type GCOp struct { 1472 OpCommon 1473 1474 // LatestRev is the most recent MD revision that was 1475 // garbage-collected with this operation. 1476 // 1477 // The codec name overrides the one for RefBlocks in OpCommon, 1478 // which GCOp doesn't use. 1479 LatestRev kbfsmd.Revision `codec:"r"` 1480 } 1481 1482 func newGCOp(latestRev kbfsmd.Revision) *GCOp { 1483 gco := &GCOp{ 1484 LatestRev: latestRev, 1485 } 1486 return gco 1487 } 1488 1489 func (gco *GCOp) deepCopy() op { 1490 gcoCopy := *gco 1491 gcoCopy.OpCommon = gco.OpCommon.deepCopy() 1492 return &gcoCopy 1493 } 1494 1495 // SizeExceptUpdates implements op. 1496 func (gco *GCOp) SizeExceptUpdates() uint64 { 1497 return data.BPSize * uint64(len(gco.UnrefBlocks)) 1498 } 1499 1500 func (gco *GCOp) allUpdates() []blockUpdate { 1501 return gco.Updates 1502 } 1503 1504 func (gco *GCOp) checkValid() error { 1505 return gco.checkUpdatesValid() 1506 } 1507 1508 func (gco *GCOp) String() string { 1509 return fmt.Sprintf("gc %d", gco.LatestRev) 1510 } 1511 1512 // Plaintext implements op. 1513 func (gco *GCOp) Plaintext() string { 1514 return gco.String() 1515 } 1516 1517 // StringWithRefs implements the op interface for GCOp. 1518 func (gco *GCOp) StringWithRefs(indent string) string { 1519 res := gco.String() + "\n" 1520 res += gco.stringWithRefs(indent) 1521 return res 1522 } 1523 1524 // checkConflict implements op. 1525 func (gco *GCOp) checkConflict( 1526 ctx context.Context, renamer ConflictRenamer, mergedOp op, 1527 isFile bool) (crAction, error) { 1528 return nil, nil 1529 } 1530 1531 // getDefaultAction implements op. 1532 func (gco *GCOp) getDefaultAction(mergedPath data.Path) crAction { 1533 return nil 1534 } 1535 1536 // invertOpForLocalNotifications returns an operation that represents 1537 // an undoing of the effect of the given op. These are intended to be 1538 // used for local notifications only, and would not be useful for 1539 // finding conflicts (for example, we lose information about the type 1540 // of the file in a rmOp that we are trying to re-create). 1541 func invertOpForLocalNotifications(oldOp op) (newOp op, err error) { 1542 switch op := oldOp.(type) { 1543 default: 1544 panic(fmt.Sprintf("Unrecognized operation: %v", op)) 1545 case *createOp: 1546 newOp, err = newRmOp(op.NewName, op.Dir.Ref, op.Type) 1547 if err != nil { 1548 return nil, err 1549 } 1550 case *rmOp: 1551 // Guess at the type, shouldn't be used for local notification 1552 // purposes. 1553 newOp, err = newCreateOp(op.OldName, op.Dir.Ref, data.File) 1554 if err != nil { 1555 return nil, err 1556 } 1557 case *renameOp: 1558 newDirRef := op.NewDir.Ref 1559 if op.NewDir == (blockUpdate{}) { 1560 newDirRef = op.OldDir.Ref 1561 } 1562 newOp, err = newRenameOp(op.NewName, newDirRef, 1563 op.OldName, op.OldDir.Ref, op.Renamed, op.RenamedType) 1564 if err != nil { 1565 return nil, err 1566 } 1567 case *syncOp: 1568 // Just replay the writes; for notifications purposes, they 1569 // will do the right job of marking the right bytes as 1570 // invalid. 1571 so, err := newSyncOp(op.File.Ref) 1572 if err != nil { 1573 return nil, err 1574 } 1575 so.Writes = make([]WriteRange, len(op.Writes)) 1576 copy(so.Writes, op.Writes) 1577 newOp = so 1578 case *setAttrOp: 1579 newOp, err = newSetAttrOp(op.Name, op.Dir.Ref, op.Attr, op.File) 1580 if err != nil { 1581 return nil, err 1582 } 1583 case *GCOp: 1584 newOp = newGCOp(op.LatestRev) 1585 case *resolutionOp: 1586 newOp = newResolutionOp() 1587 case *rekeyOp: 1588 newOp = newRekeyOp() 1589 } 1590 newOp.setFinalPath(oldOp.getFinalPath()) 1591 // Now reverse all the block updates. Don't bother with bare Refs 1592 // and Unrefs since they don't matter for local notification 1593 // purposes. 1594 for _, update := range oldOp.allUpdates() { 1595 newOp.AddUpdate(update.Ref, update.Unref) 1596 } 1597 return newOp, nil 1598 } 1599 1600 // NOTE: If you're updating opPointerizer and RegisterOps, make sure 1601 // to also update opPointerizerFuture and registerOpsFuture in 1602 // ops_test.go. 1603 1604 // Our ugorji codec cannot decode our extension types as pointers, and 1605 // we need them to be pointers so they correctly satisfy the op 1606 // interface. So this function simply converts them into pointers as 1607 // needed. 1608 func opPointerizer(iface interface{}) reflect.Value { 1609 switch op := iface.(type) { 1610 default: 1611 return reflect.ValueOf(iface) 1612 case createOp: 1613 return reflect.ValueOf(&op) 1614 case rmOp: 1615 return reflect.ValueOf(&op) 1616 case renameOp: 1617 return reflect.ValueOf(&op) 1618 case syncOp: 1619 return reflect.ValueOf(&op) 1620 case setAttrOp: 1621 return reflect.ValueOf(&op) 1622 case resolutionOp: 1623 return reflect.ValueOf(&op) 1624 case rekeyOp: 1625 return reflect.ValueOf(&op) 1626 case GCOp: 1627 return reflect.ValueOf(&op) 1628 } 1629 } 1630 1631 // RegisterOps registers all op types with the given codec. 1632 func RegisterOps(codec kbfscodec.Codec) { 1633 codec.RegisterType(reflect.TypeOf(createOp{}), createOpCode) 1634 codec.RegisterType(reflect.TypeOf(rmOp{}), rmOpCode) 1635 codec.RegisterType(reflect.TypeOf(renameOp{}), renameOpCode) 1636 codec.RegisterType(reflect.TypeOf(syncOp{}), syncOpCode) 1637 codec.RegisterType(reflect.TypeOf(setAttrOp{}), setAttrOpCode) 1638 codec.RegisterType(reflect.TypeOf(resolutionOp{}), resolutionOpCode) 1639 codec.RegisterType(reflect.TypeOf(rekeyOp{}), rekeyOpCode) 1640 codec.RegisterType(reflect.TypeOf(GCOp{}), gcOpCode) 1641 codec.RegisterIfaceSliceType(reflect.TypeOf(opsList{}), opsListCode, 1642 opPointerizer) 1643 } 1644 1645 // pathSortedOps sorts the ops in increasing order by path length, so 1646 // e.g. file creates come before file modifies. 1647 type pathSortedOps []op 1648 1649 func (pso pathSortedOps) Len() int { 1650 return len(pso) 1651 } 1652 1653 func (pso pathSortedOps) Less(i, j int) bool { 1654 return len(pso[i].getFinalPath().Path) < len(pso[j].getFinalPath().Path) 1655 } 1656 1657 func (pso pathSortedOps) Swap(i, j int) { 1658 pso[i], pso[j] = pso[j], pso[i] 1659 }