github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/folder_branch_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 "fmt" 9 "io" 10 "math/rand" 11 "os" 12 stdpath "path" 13 "path/filepath" 14 "reflect" 15 "sort" 16 "strings" 17 "sync" 18 "time" 19 20 "github.com/keybase/backoff" 21 "github.com/keybase/client/go/kbfs/data" 22 "github.com/keybase/client/go/kbfs/env" 23 "github.com/keybase/client/go/kbfs/idutil" 24 "github.com/keybase/client/go/kbfs/kbfsblock" 25 "github.com/keybase/client/go/kbfs/kbfscrypto" 26 "github.com/keybase/client/go/kbfs/kbfsedits" 27 "github.com/keybase/client/go/kbfs/kbfsmd" 28 "github.com/keybase/client/go/kbfs/kbfssync" 29 "github.com/keybase/client/go/kbfs/libcontext" 30 "github.com/keybase/client/go/kbfs/libkey" 31 "github.com/keybase/client/go/kbfs/tlf" 32 "github.com/keybase/client/go/kbfs/tlfhandle" 33 kbname "github.com/keybase/client/go/kbun" 34 "github.com/keybase/client/go/libkb" 35 "github.com/keybase/client/go/protocol/chat1" 36 "github.com/keybase/client/go/protocol/keybase1" 37 "github.com/keybase/go-framed-msgpack-rpc/rpc" 38 "github.com/pkg/errors" 39 "golang.org/x/net/context" 40 ) 41 42 // mdReadType indicates whether a read needs identifies. 43 type mdReadType int 44 45 const ( 46 // A read request that doesn't need an identify to be 47 // performed. 48 mdReadNoIdentify mdReadType = iota 49 // A read request that needs an identify to be performed (if 50 // it hasn't been already). 51 mdReadNeedIdentify 52 ) 53 54 // mdUpdateType indicates update type. 55 type mdUpdateType int 56 57 const ( 58 mdWrite mdUpdateType = iota 59 // A rekey request. Doesn't need an identify to be performed, as 60 // a rekey does its own (finer-grained) identifies. 61 mdRekey 62 ) 63 64 type branchType int 65 66 const ( 67 standard branchType = iota // an online, read-write branch 68 archive // an online, read-only branch 69 conflict // a cleared, local conflict branch 70 ) 71 72 // Constants used in this file. TODO: Make these configurable? 73 const ( 74 // Maximum number of blocks that can be sent in parallel 75 maxParallelBlockPuts = 100 76 // Max response size for a single DynamoDB query is 1MB. 77 maxMDsAtATime = 10 78 // Cap the number of times we retry after a recoverable error 79 maxRetriesOnRecoverableErrors = 10 80 // If it's been more than this long since our last update, check 81 // the current head before downloading all of the new revisions. 82 fastForwardTimeThresh = 15 * time.Minute 83 // If there are more than this many new revisions, fast forward 84 // rather than downloading them all. 85 fastForwardRevThresh = 50 86 // Period between mark-and-sweep attempts. 87 markAndSweepPeriod = 1 * time.Hour 88 // A hard-coded reason used to derive the log obfuscator secret. 89 obfuscatorDerivationString = "Keybase-Derived-KBFS-Log-Obfuscator-1" 90 // How long do we skip identifies after seeing one with a broken 91 // proof warning? 92 cacheBrokenProofIdentifiesDuration = 5 * time.Minute 93 ) 94 95 // ErrStillStagedAfterCR indicates that conflict resolution failed to take 96 // the FBO out of staging. 97 type ErrStillStagedAfterCR struct{} 98 99 // Error implements the error interface. 100 func (*ErrStillStagedAfterCR) Error() string { 101 return "conflict resolution didn't take us out of staging" 102 } 103 104 type fboMutexLevel kbfssync.MutexLevel 105 106 const ( 107 fboMDWriter fboMutexLevel = 1 108 fboHead fboMutexLevel = 2 109 fboBlock fboMutexLevel = 3 110 fboSync fboMutexLevel = 4 111 ) 112 113 func (o fboMutexLevel) String() string { 114 switch o { 115 case fboMDWriter: 116 return "mdWriterLock" 117 case fboHead: 118 return "headLock" 119 case fboBlock: 120 return "blockLock" 121 case fboSync: 122 return "syncLock" 123 default: 124 return fmt.Sprintf("Invalid fboMutexLevel %d", int(o)) 125 } 126 } 127 128 func fboMutexLevelToString(o kbfssync.MutexLevel) string { 129 return (fboMutexLevel(o)).String() 130 } 131 132 // Rules for working with kbfssync.LockState in FBO: 133 // 134 // - Every "execution flow" (i.e., program flow that happens 135 // sequentially) needs its own lockState object. This usually means 136 // that each "public" FBO method does: 137 // 138 // lState := makeFBOLockState() 139 // 140 // near the top. 141 // 142 // - Plumb lState through to all functions that hold any of the 143 // relevant locks, or are called under those locks. 144 // 145 // This way, violations of the lock hierarchy will be detected at 146 // runtime. 147 148 func makeFBOLockState() *kbfssync.LockState { 149 return kbfssync.MakeLevelState(fboMutexLevelToString) 150 } 151 152 // blockLock is just like a sync.RWMutex, but with an extra operation 153 // (DoRUnlockedIfPossible). 154 type blockLock struct { 155 kbfssync.LeveledRWMutex 156 locked bool 157 } 158 159 func (bl *blockLock) Lock(lState *kbfssync.LockState) { 160 bl.LeveledRWMutex.Lock(lState) 161 bl.locked = true 162 } 163 164 func (bl *blockLock) Unlock(lState *kbfssync.LockState) { 165 bl.locked = false 166 bl.LeveledRWMutex.Unlock(lState) 167 } 168 169 // DoRUnlockedIfPossible must be called when r- or w-locked. If 170 // r-locked, r-unlocks, runs the given function, and r-locks after 171 // it's done. Otherwise, just runs the given function. 172 func (bl *blockLock) DoRUnlockedIfPossible( 173 lState *kbfssync.LockState, f func(*kbfssync.LockState)) { 174 if !bl.locked { 175 bl.RUnlock(lState) 176 defer bl.RLock(lState) 177 } 178 179 f(lState) 180 } 181 182 // headTrustStatus marks whether the head is from a trusted or 183 // untrusted source. When rekeying we get the head MD by folder id 184 // and do not check the tlf handle 185 type headTrustStatus int 186 187 const ( 188 headUntrusted headTrustStatus = iota 189 headTrusted 190 ) 191 192 type cachedDirOp struct { 193 dirOp op 194 nodes []Node 195 } 196 197 type editChannelActivity struct { 198 convID chat1.ConversationID // set to nil to force a re-init 199 name string 200 message string 201 } 202 203 // folderBranchOps implements the KBFSOps interface for a specific 204 // branch of a specific folder. It is go-routine safe for operations 205 // within the folder. 206 // 207 // We use locks to protect against multiple goroutines accessing the 208 // same folder-branch. The goal with our locking strategy is maximize 209 // concurrent access whenever possible. See design/state_machine.md 210 // for more details. There are three important locks: 211 // 212 // 1. mdWriterLock: Any "remote-sync" operation (one which modifies the 213 // folder's metadata) must take this lock during the entirety of 214 // its operation, to avoid forking the MD. 215 // 216 // 2. headLock: This is a read/write mutex. It must be taken for 217 // reading before accessing any part of the current head MD. It 218 // should be taken for the shortest time possible -- that means in 219 // general that it should be taken, and the MD copied to a 220 // goroutine-local variable, and then it can be released. 221 // Remote-sync operations should take it for writing after pushing 222 // all of the blocks and MD to the KBFS servers (i.e., all network 223 // accesses), and then hold it until after all notifications have 224 // been fired, to ensure that no concurrent "local" operations ever 225 // see inconsistent state locally. 226 // 227 // 3. blockLock: This too is a read/write mutex. It must be taken for 228 // reading before accessing any blocks in the block cache that 229 // belong to this folder/branch. This includes checking their 230 // dirty status. It should be taken for the shortest time possible 231 // -- that means in general it should be taken, and then the blocks 232 // that will be modified should be copied to local variables in the 233 // goroutine, and then it should be released. The blocks should 234 // then be modified locally, and then readied and pushed out 235 // remotely. Only after the blocks have been pushed to the server 236 // should a remote-sync operation take the lock again (this time 237 // for writing) and put/finalize the blocks. Write and Truncate 238 // should take blockLock for their entire lifetime, since they 239 // don't involve writes over the network. Furthermore, if a block 240 // is not in the cache and needs to be fetched, we should release 241 // the mutex before doing the network operation, and lock it again 242 // before writing the block back to the cache. 243 // 244 // We want to allow writes and truncates to a file that's currently 245 // being sync'd, like any good networked file system. The tricky part 246 // is making sure the changes can both: a) be read while the sync is 247 // happening, and b) be applied to the new file path after the sync is 248 // done. 249 // 250 // For now, we just do the dumb, brute force thing for now: if a block 251 // is currently being sync'd, it copies the block and puts it back 252 // into the cache as modified. Then, when the sync finishes, it 253 // throws away the modified blocks and re-applies the change to the 254 // new file path (which might have a completely different set of 255 // blocks, so we can't just reuse the blocks that were modified during 256 // the sync.) 257 type folderBranchOps struct { 258 config Config 259 folderBranch data.FolderBranch 260 unmergedBID kbfsmd.BranchID // protected by mdWriterLock 261 bType branchType 262 observers *observerList 263 syncedTlfObservers *syncedTlfObserverList 264 serviceStatus *kbfsCurrentStatus 265 favs *Favorites 266 267 // The leveled locks below, when locked concurrently by the same 268 // goroutine, should only be taken in the following order to avoid 269 // deadlock. 270 271 // Taken by any method making MD modifications. 272 mdWriterLock kbfssync.LeveledMutex 273 dirOps []cachedDirOp 274 275 // protects access to head, headStatus, latestMergedRevision, 276 // and hasBeenCleared. 277 headLock kbfssync.LeveledRWMutex 278 head ImmutableRootMetadata 279 headStatus headTrustStatus 280 // latestMergedRevision tracks the latest heard merged revision on server 281 latestMergedRevision kbfsmd.Revision 282 latestMergedUpdated chan struct{} 283 // Has this folder ever been cleared? 284 hasBeenCleared bool 285 partialSyncRev kbfsmd.Revision 286 partialSyncConfig keybase1.FolderSyncConfig 287 288 syncLock kbfssync.LeveledRWMutex 289 markAndSweepTrigger chan<- struct{} 290 291 blocks folderBlockOps 292 prepper folderUpdatePrepper 293 294 // nodeCache itself is goroutine-safe, but this object's use 295 // of it has special requirements: 296 // 297 // - Reads can call PathFromNode() unlocked, since there are 298 // no guarantees with concurrent reads. 299 // 300 // - Operations that takes mdWriterLock always needs the 301 // most up-to-date paths, so those must call 302 // PathFromNode() under mdWriterLock. 303 // 304 // - Block write operations (write/truncate/sync) need to 305 // coordinate. Specifically, sync must make sure that 306 // blocks referenced in a path (including all of the child 307 // blocks) must exist in the cache during calls to 308 // PathFromNode from write/truncate. This means that sync 309 // must modify dirty file blocks only under blockLock, and 310 // write/truncate must call PathFromNode() under 311 // blockLock. 312 // 313 // Furthermore, calls to UpdatePointer() must happen 314 // before the copy-on-write mode induced by Sync() is 315 // finished. 316 nodeCache NodeCache 317 318 // Whether we've identified this TLF or not. 319 identifyLock sync.Mutex 320 identifyDone bool 321 identifyTime time.Time 322 identifyDoneWithWarning bool 323 324 // The current status summary for this folder 325 status *folderBranchStatusKeeper 326 327 // How to log 328 log traceLogger 329 deferLog traceLogger 330 defer2Log traceLogger 331 vlog *libkb.VDebugLog 332 deferVlog *libkb.VDebugLog 333 defer2Vlog *libkb.VDebugLog 334 335 // Closed on shutdown 336 shutdownChan chan struct{} 337 // Wait on this once we're done shutting down. Any goroutine that logs must 338 // be registered with this WaitGroup, to avoid test races. 339 doneWg sync.WaitGroup 340 341 // Can be used to turn off notifications for a while (e.g., for testing) 342 updatePauseChan chan (<-chan struct{}) 343 344 cancelUpdatesLock sync.Mutex 345 // Cancels the goroutine currently waiting on TLF MD updates. 346 cancelUpdates context.CancelFunc 347 348 // This channel will be closed when the register goroutine completes. 349 updateDoneChan chan struct{} 350 351 // forceSyncChan is read from by the background sync process 352 // to know when it should sync immediately. 353 forceSyncChan <-chan struct{} 354 355 // syncNeededChan is signalled when a buffered write happens, and 356 // lets the background syncer wait rather than waking up all the 357 // time. 358 syncNeededChan chan struct{} 359 360 // How to resolve conflicts 361 cr *ConflictResolver 362 363 // Helper class for archiving and cleaning up the blocks for this TLF 364 fbm *folderBlockManager 365 366 rekeyFSM RekeyFSM 367 368 editHistory *kbfsedits.TlfHistory 369 editChannels chan editChannelActivity 370 refreshEditHistoryChannel chan struct{} 371 372 editsLock sync.Mutex 373 // Cancels the goroutine currently waiting on edits 374 cancelEdits context.CancelFunc 375 // This channel gets created when chat monitoring starts, and 376 // closed when it ends, so we can avoid sending messages to the 377 // monitorer when they won't be read. 378 editMonitoringInProgress chan struct{} 379 launchEditMonitor sync.Once 380 381 branchChanges kbfssync.RepeatedWaitGroup 382 mdFlushes kbfssync.RepeatedWaitGroup 383 forcedFastForwards kbfssync.RepeatedWaitGroup 384 editActivity kbfssync.RepeatedWaitGroup 385 partialSyncs kbfssync.RepeatedWaitGroup 386 rootWaits kbfssync.RepeatedWaitGroup 387 388 muLastGetHead sync.Mutex 389 // We record a timestamp everytime getHead or getTrustedHead is called, and 390 // use this as a heuristic for whether user is actively using KBFS. If user 391 // has been generating KBFS activities recently, it makes sense to try to 392 // reconnect as soon as possible in case of a deployment causes 393 // disconnection. 394 lastGetHead time.Time 395 396 convLock sync.Mutex 397 convID chat1.ConversationID 398 399 obLock sync.RWMutex 400 obSecret data.NodeObfuscatorSecret 401 } 402 403 var _ fbmHelper = (*folderBranchOps)(nil) 404 405 // newFolderBranchOps constructs a new folderBranchOps object. 406 func newFolderBranchOps( 407 ctx context.Context, appStateUpdater env.AppStateUpdater, 408 config Config, fb data.FolderBranch, 409 bType branchType, 410 serviceStatus *kbfsCurrentStatus, favs *Favorites, 411 syncedTlfObservers *syncedTlfObserverList) *folderBranchOps { 412 var nodeCache NodeCache 413 if config.Mode().NodeCacheEnabled() { 414 nodeCache = newNodeCacheStandard(fb) 415 for _, f := range config.RootNodeWrappers() { 416 nodeCache.AddRootWrapper(f) 417 } 418 if bType == archive || bType == conflict { 419 nodeCache.AddRootWrapper(readonlyWrapper) 420 } 421 } 422 423 if bType == standard && fb.Branch != data.MasterBranch { 424 panic("standard FBOs must use the master branch") 425 } else if bType != standard && fb.Branch == data.MasterBranch { 426 panic("non-standard FBOs must not use the master branch") 427 } 428 429 // make logger 430 branchSuffix := "" 431 if fb.Branch != data.MasterBranch { 432 branchSuffix = " " + string(fb.Branch) 433 } 434 tlfStringFull := fb.Tlf.String() 435 // Shorten the TLF ID for the module name. 8 characters should be 436 // unique enough for a local node. 437 log := config.MakeLogger(fmt.Sprintf("FBO %s%s", tlfStringFull[:8], 438 branchSuffix)) 439 deferLog := log.CloneWithAddedDepth(1) 440 defer2Log := log.CloneWithAddedDepth(2) 441 442 // But print it out once in full, just in case. 443 log.CInfof(ctx, "Created new folder-branch for %s", tlfStringFull) 444 445 observers := newObserverList() 446 447 mdWriterLock := kbfssync.MakeLeveledMutex( 448 kbfssync.MutexLevel(fboMDWriter), &sync.Mutex{}) 449 headLock := kbfssync.MakeLeveledRWMutex( 450 kbfssync.MutexLevel(fboHead), &sync.RWMutex{}) 451 blockLockMu := kbfssync.MakeLeveledRWMutex( 452 kbfssync.MutexLevel(fboBlock), &sync.RWMutex{}) 453 syncLock := kbfssync.MakeLeveledRWMutex( 454 kbfssync.MutexLevel(fboSync), &sync.RWMutex{}) 455 456 forceSyncChan := make(chan struct{}) 457 458 fbo := &folderBranchOps{ 459 config: config, 460 folderBranch: fb, 461 unmergedBID: kbfsmd.BranchID{}, 462 bType: bType, 463 observers: observers, 464 syncedTlfObservers: syncedTlfObservers, 465 serviceStatus: serviceStatus, 466 favs: favs, 467 status: newFolderBranchStatusKeeper( 468 config, nodeCache, fb.Tlf.Bytes()), 469 mdWriterLock: mdWriterLock, 470 headLock: headLock, 471 syncLock: syncLock, 472 blocks: folderBlockOps{ 473 config: config, 474 log: log, 475 vlog: config.MakeVLogger(log), 476 folderBranch: fb, 477 observers: observers, 478 forceSyncChan: forceSyncChan, 479 blockLock: blockLock{ 480 LeveledRWMutex: blockLockMu, 481 }, 482 dirtyFiles: make(map[data.BlockPointer]*data.DirtyFile), 483 deferred: make(map[data.BlockRef]deferredState), 484 unrefCache: make(map[data.BlockRef]*syncInfo), 485 dirtyDirs: make(map[data.BlockPointer][]data.BlockInfo), 486 nodeCache: nodeCache, 487 }, 488 nodeCache: nodeCache, 489 log: traceLogger{log}, 490 deferLog: traceLogger{deferLog}, 491 defer2Log: traceLogger{defer2Log}, 492 vlog: config.MakeVLogger(log), 493 deferVlog: config.MakeVLogger(deferLog), 494 defer2Vlog: config.MakeVLogger(defer2Log), 495 shutdownChan: make(chan struct{}), 496 updatePauseChan: make(chan (<-chan struct{})), 497 forceSyncChan: forceSyncChan, 498 syncNeededChan: make(chan struct{}, 1), 499 editHistory: kbfsedits.NewTlfHistory(), 500 editChannels: make(chan editChannelActivity, 100), 501 refreshEditHistoryChannel: make(chan struct{}, 1), 502 } 503 fbo.prepper = folderUpdatePrepper{ 504 config: config, 505 folderBranch: fb, 506 blocks: &fbo.blocks, 507 log: log, 508 vlog: config.MakeVLogger(log), 509 } 510 fbo.cr = NewConflictResolver(config, fbo) 511 fbo.fbm = newFolderBlockManager(appStateUpdater, config, fb, bType, fbo) 512 fbo.rekeyFSM = NewRekeyFSM(fbo) 513 if config.DoBackgroundFlushes() && bType == standard { 514 fbo.goTracked(fbo.backgroundFlusher) 515 } 516 if config.Mode().NodeCacheEnabled() { 517 nodeCache.SetObfuscatorMaker(fbo.makeObfuscator) 518 } 519 520 return fbo 521 } 522 523 func (fbo *folderBranchOps) goTracked(f func()) { 524 fbo.doneWg.Add(1) 525 go func() { 526 defer fbo.doneWg.Done() 527 f() 528 }() 529 } 530 531 // markForReIdentifyIfNeeded checks whether this tlf is identified and mark 532 // it for lazy reidentification if it exceeds time limits. 533 func (fbo *folderBranchOps) markForReIdentifyIfNeeded(now time.Time, maxValid time.Duration) { 534 fbo.identifyLock.Lock() 535 defer fbo.identifyLock.Unlock() 536 if fbo.identifyDone && (now.Before(fbo.identifyTime) || fbo.identifyTime.Add(maxValid).Before(now)) { 537 fbo.log.CDebugf( 538 context.TODO(), "Expiring identify from %v", fbo.identifyTime) 539 fbo.identifyDone = false 540 } 541 } 542 543 // Shutdown safely shuts down any background goroutines that may have 544 // been launched by folderBranchOps. 545 func (fbo *folderBranchOps) Shutdown(ctx context.Context) error { 546 if fbo.config.CheckStateOnShutdown() { 547 lState := makeFBOLockState() 548 549 switch { 550 case fbo.blocks.GetState(lState) == dirtyState: 551 fbo.log.CDebugf(ctx, "Skipping state-checking due to dirty state") 552 case fbo.isUnmerged(lState): 553 fbo.log.CDebugf(ctx, "Skipping state-checking due to being staged") 554 default: 555 // Make sure we're up to date first 556 if err := fbo.SyncFromServer(ctx, 557 fbo.folderBranch, nil); err != nil { 558 return err 559 } 560 561 // Check the state for consistency before shutting down. 562 sc := NewStateChecker(fbo.config) 563 if err := sc.CheckMergedState(ctx, fbo.id()); err != nil { 564 return err 565 } 566 } 567 } 568 569 if err := fbo.fbm.waitForArchives(ctx); err != nil { 570 return err 571 } 572 573 close(fbo.shutdownChan) 574 fbo.cr.Shutdown() 575 fbo.fbm.shutdown() 576 fbo.rekeyFSM.Shutdown() 577 // Wait for all the goroutines to finish, so that we don't have any races 578 // with logging during test reporting. 579 fbo.doneWg.Wait() 580 581 fbo.config.MDServer().CancelRegistration(ctx, fbo.id()) 582 return nil 583 } 584 585 func (fbo *folderBranchOps) id() tlf.ID { 586 return fbo.folderBranch.Tlf 587 } 588 589 func (fbo *folderBranchOps) oa() keybase1.OfflineAvailability { 590 return fbo.config.OfflineAvailabilityForID(fbo.id()) 591 } 592 593 func (fbo *folderBranchOps) branch() data.BranchName { 594 return fbo.folderBranch.Branch 595 } 596 597 func (fbo *folderBranchOps) addToFavorites( 598 ctx context.Context, created bool) (err error) { 599 lState := makeFBOLockState() 600 head := fbo.getTrustedHead(ctx, lState, mdNoCommit) 601 if head == (ImmutableRootMetadata{}) { 602 return OpsCantHandleFavorite{"Can't add a favorite without a handle"} 603 } 604 605 return fbo.addToFavoritesByHandle(ctx, head.GetTlfHandle(), created) 606 } 607 608 func (fbo *folderBranchOps) addToFavoritesByHandle( 609 ctx context.Context, handle *tlfhandle.Handle, created bool) (err error) { 610 if _, err := fbo.config.KBPKI().GetCurrentSession(ctx); err != nil { 611 // Can't favorite while not logged in 612 return nil 613 } 614 615 fbo.favs.AddAsync(ctx, handle.ToFavToAdd(created)) 616 return nil 617 } 618 619 func (fbo *folderBranchOps) deleteFromFavorites(ctx context.Context) error { 620 if _, err := fbo.config.KBPKI().GetCurrentSession(ctx); err != nil { 621 // Can't unfavorite while not logged in 622 return nil 623 } 624 625 lState := makeFBOLockState() 626 head := fbo.getTrustedHead(ctx, lState, mdNoCommit) 627 if head == (ImmutableRootMetadata{}) { 628 // This can happen when identifies fail and the head is never set. 629 return OpsCantHandleFavorite{"Can't delete a favorite without a handle"} 630 } 631 632 h := head.GetTlfHandle() 633 return fbo.favs.Delete(ctx, h.ToFavorite()) 634 } 635 636 func (fbo *folderBranchOps) doFavoritesOp( 637 ctx context.Context, fop FavoritesOp, handle *tlfhandle.Handle) error { 638 if fbo.bType == conflict { 639 // Ignore local conflicts, they should never be added as real favorites. 640 return nil 641 } 642 switch fop { 643 case FavoritesOpNoChange: 644 return nil 645 case FavoritesOpAdd: 646 if handle != nil { 647 return fbo.addToFavoritesByHandle(ctx, handle, false) 648 } 649 return fbo.addToFavorites(ctx, false) 650 case FavoritesOpAddNewlyCreated: 651 if handle != nil { 652 return fbo.addToFavoritesByHandle(ctx, handle, true) 653 } 654 return fbo.addToFavorites(ctx, true) 655 case FavoritesOpRemove: 656 return fbo.deleteFromFavorites(ctx) 657 default: 658 return InvalidFavoritesOpError{} 659 } 660 } 661 662 func (fbo *folderBranchOps) updateLastGetHeadTimestamp() { 663 fbo.muLastGetHead.Lock() 664 defer fbo.muLastGetHead.Unlock() 665 fbo.lastGetHead = fbo.config.Clock().Now() 666 } 667 668 type mdCommitType int 669 670 const ( 671 mdCommit mdCommitType = iota 672 mdNoCommit 673 ) 674 675 func (fbo *folderBranchOps) commitHeadLocked( 676 ctx context.Context, lState *kbfssync.LockState, ct mdCommitType) { 677 fbo.headLock.AssertRLocked(lState) 678 if ct == mdNoCommit { 679 return 680 } 681 diskMDCache := fbo.config.DiskMDCache() 682 if diskMDCache == nil { 683 return 684 } 685 686 if !fbo.head.putToServer { 687 return 688 } 689 rev := fbo.head.Revision() 690 691 id := fbo.id() 692 log := fbo.log 693 fbo.goTracked(func() { 694 err := diskMDCache.Commit(context.Background(), id, rev) 695 if err != nil { 696 log.CDebugf(ctx, "Error commiting revision %d: %+v", rev, err) 697 } 698 }) 699 } 700 701 // getTrustedHead should not be called outside of folder_branch_ops.go. 702 // Returns ImmutableRootMetadata{} when the head is not trusted. 703 // See the comment on headTrustedStatus for more information. 704 func (fbo *folderBranchOps) getTrustedHead( 705 ctx context.Context, lState *kbfssync.LockState, 706 ct mdCommitType) ImmutableRootMetadata { 707 fbo.headLock.RLock(lState) 708 defer fbo.headLock.RUnlock(lState) 709 if fbo.headStatus == headUntrusted { 710 return ImmutableRootMetadata{} 711 } 712 713 // This triggers any mdserver backoff timer to fast forward. In case of a 714 // deployment, this causes KBFS client to try to reconnect to mdserver 715 // immediately rather than waiting until the random backoff timer is up. 716 // Note that this doesn't necessarily guarantee that the fbo handler that 717 // called this method would get latest MD. 718 fbo.config.MDServer().FastForwardBackoff() 719 fbo.updateLastGetHeadTimestamp() 720 fbo.commitHeadLocked(ctx, lState, ct) 721 722 return fbo.head 723 } 724 725 // getHead should not be called outside of folder_branch_ops.go. 726 func (fbo *folderBranchOps) getHead( 727 ctx context.Context, lState *kbfssync.LockState, ct mdCommitType) ( 728 ImmutableRootMetadata, headTrustStatus) { 729 fbo.headLock.RLock(lState) 730 defer fbo.headLock.RUnlock(lState) 731 732 // See getTrustedHead for explanation. 733 fbo.config.MDServer().FastForwardBackoff() 734 fbo.updateLastGetHeadTimestamp() 735 fbo.commitHeadLocked(ctx, lState, ct) 736 737 return fbo.head, fbo.headStatus 738 } 739 740 // isUnmerged should not be called if mdWriterLock is already taken. 741 func (fbo *folderBranchOps) isUnmerged(lState *kbfssync.LockState) bool { 742 fbo.mdWriterLock.Lock(lState) 743 defer fbo.mdWriterLock.Unlock(lState) 744 return fbo.unmergedBID != kbfsmd.NullBranchID 745 } 746 747 func (fbo *folderBranchOps) isUnmergedLocked(lState *kbfssync.LockState) bool { 748 fbo.mdWriterLock.AssertLocked(lState) 749 750 return fbo.unmergedBID != kbfsmd.NullBranchID 751 } 752 753 var errJournalNotAvailable = errors.New("could not get journal for TLF") 754 755 // clearConflictView tells the journal to move any pending writes elsewhere, 756 // resets the CR counter, and resets the FBO to have a synced view of the TLF. 757 func (fbo *folderBranchOps) clearConflictView(ctx context.Context) ( 758 err error) { 759 // TODO(KBFS-3990): show the cleared conflict view under a special path, 760 // so users can copy any unmerged files manually back into their synced 761 // view before nuking it. 762 763 fbo.log.CDebugf(ctx, "Clearing conflict view") 764 defer func() { 765 fbo.deferLog.CDebugf(ctx, "Done with clearConflictView: %+v", err) 766 }() 767 768 lState := makeFBOLockState() 769 fbo.mdWriterLock.Lock(lState) 770 defer fbo.mdWriterLock.Unlock(lState) 771 772 journalEnabled := TLFJournalEnabled(fbo.config, fbo.id()) 773 if journalEnabled { 774 err = fbo.unstageLocked(ctx, lState, moveJournalsAway) 775 } else { 776 err = fbo.unstageLocked(ctx, lState, doPruneBranches) 777 } 778 if err != nil { 779 return err 780 } 781 return fbo.cr.clearConflictRecords(ctx) 782 } 783 784 // forceStuckConflictForTesting forces the TLF into a stuck conflict 785 // view, for testing. 786 func (fbo *folderBranchOps) forceStuckConflictForTesting( 787 ctx context.Context) (err error) { 788 startTime, timer := fbo.startOp(ctx, "Forcing a stuck conflict") 789 defer func() { 790 fbo.endOp( 791 ctx, startTime, timer, "Forcing a stuck conflict done: %+v", err) 792 }() 793 794 lState := makeFBOLockState() 795 fbo.mdWriterLock.Lock(lState) 796 defer fbo.mdWriterLock.Unlock(lState) 797 798 if fbo.isUnmergedLocked(lState) { 799 return errors.New("Cannot force conflict when already unmerged") 800 } 801 802 // Disable updates. 803 unpauseUpdatesCh := make(chan struct{}) 804 select { 805 case fbo.updatePauseChan <- unpauseUpdatesCh: 806 case <-ctx.Done(): 807 return ctx.Err() 808 } 809 defer func() { unpauseUpdatesCh <- struct{}{} }() 810 811 // Make a no-op revision with an empty resolutionOp. Wait for it 812 // to flush to the server. 813 origHead, _ := fbo.getHead(ctx, lState, mdNoCommit) 814 mergedGCOp := newGCOp(origHead.data.LastGCRevision) 815 err = fbo.finalizeGCOpLocked(ctx, lState, mergedGCOp) 816 if err != nil { 817 return err 818 } 819 820 jManager, _ := GetJournalManager(fbo.config) 821 if jManager != nil { 822 err := fbo.waitForJournalLocked(ctx, lState, jManager) 823 if err != nil { 824 return err 825 } 826 // Wait for the flush handler to finish, so we don't 827 // accidentally swap in the upcoming MD on the conflict branch 828 // over the "merged" one we just flushed, before the pointer 829 // archiving step happens. 830 err = fbo.mdFlushes.Wait(ctx) 831 if err != nil { 832 return err 833 } 834 } 835 836 // Roll back the local view to the original revision. 837 err = func() error { 838 fbo.headLock.Lock(lState) 839 defer fbo.headLock.Unlock(lState) 840 err = fbo.setHeadLocked(ctx, lState, origHead, headTrusted, mdNoCommit) 841 if err != nil { 842 return err 843 } 844 fbo.setLatestMergedRevisionLocked( 845 ctx, lState, origHead.Revision(), true) 846 return nil 847 }() 848 if err != nil { 849 return err 850 } 851 852 // Set CR to always fail. 853 oldMode := fbo.cr.getFailModeForTesting() 854 fbo.cr.setFailModeForTesting(alwaysFailCR) 855 defer func() { fbo.cr.setFailModeForTesting(oldMode) }() 856 857 // Make fake conflicting files to trigger CR. Make one for each 858 // attempt needed to result in stuck CR. 859 handle := origHead.GetTlfHandle() 860 rootNode, err := fbo.nodeCache.GetOrCreate( 861 origHead.data.Dir.BlockPointer, 862 data.NewPathPartString(string(handle.GetCanonicalName()), 863 fbo.makeObfuscator()), 864 nil, data.Dir) 865 if err != nil { 866 return err 867 } 868 869 for i := 0; i < maxConflictResolutionAttempts+1; i++ { 870 filename := fmt.Sprintf("FILE_FOR_STUCK_CONFLICT_%02d", i) 871 _, _, err := fbo.createEntryLocked( 872 ctx, lState, rootNode, rootNode.ChildName(filename), data.File, 873 NoExcl) 874 if err != nil { 875 return err 876 } 877 878 err = fbo.syncAllLocked(ctx, lState, NoExcl) 879 if err != nil { 880 return err 881 } 882 883 if jManager != nil && TLFJournalEnabled(fbo.config, fbo.id()) { 884 // Can't use fbo.waitForJournalLocked here, since the 885 // flushing won't actually complete. 886 err := jManager.Wait(ctx, fbo.id()) 887 if err != nil { 888 return err 889 } 890 newHead, _ := fbo.getHead(ctx, lState, mdNoCommit) 891 fbo.cr.Resolve( 892 ctx, newHead.Revision(), kbfsmd.RevisionUninitialized) 893 } 894 895 err = fbo.cr.Wait(ctx) 896 if err != nil { 897 return err 898 } 899 } 900 901 // Make sure we're stuck. 902 isStuck, err := fbo.cr.isStuck() 903 if err != nil { 904 return err 905 } 906 if !isStuck { 907 return errors.New("CR not stuck after trying to force conflict") 908 } 909 910 return nil 911 } 912 913 func (fbo *folderBranchOps) setBranchIDLocked( 914 lState *kbfssync.LockState, unmergedBID kbfsmd.BranchID) { 915 fbo.mdWriterLock.AssertLocked(lState) 916 917 if fbo.unmergedBID != unmergedBID { 918 fbo.cr.BeginNewBranch() 919 } 920 921 fbo.unmergedBID = unmergedBID 922 if unmergedBID == kbfsmd.NullBranchID { 923 fbo.status.setCRSummary(nil, nil) 924 } 925 } 926 927 var errNoFlushedRevisions = errors.New("No flushed MDs yet") 928 var errNoMergedRevWhileStaged = errors.New( 929 "Cannot find most recent merged revision while staged") 930 931 func (fbo *folderBranchOps) getJournalRevisions(ctx context.Context) ( 932 predRev, journalEndRev kbfsmd.Revision, err error) { 933 jManager, err := GetJournalManager(fbo.config) 934 if err != nil { 935 // Journaling is disabled entirely. 936 return kbfsmd.RevisionUninitialized, kbfsmd.RevisionUninitialized, nil 937 } 938 939 jStatus, err := jManager.JournalStatus(fbo.id()) 940 if err != nil { 941 // Journaling is disabled for this TLF, so use the local head. 942 // TODO: JournalStatus could return other errors (likely 943 // file/disk corruption) that indicate a real problem, so it 944 // might be nice to type those errors so we can distinguish 945 // them. 946 return kbfsmd.RevisionUninitialized, kbfsmd.RevisionUninitialized, nil 947 } 948 949 if jStatus.BranchID != kbfsmd.NullBranchID.String() { 950 return kbfsmd.RevisionUninitialized, kbfsmd.RevisionUninitialized, 951 errNoMergedRevWhileStaged 952 } 953 954 if jStatus.RevisionStart == kbfsmd.RevisionUninitialized { 955 // The journal is empty, so the local head must be the most recent. 956 return kbfsmd.RevisionUninitialized, kbfsmd.RevisionUninitialized, nil 957 } else if jStatus.RevisionStart == kbfsmd.RevisionInitial { 958 // Nothing has been flushed to the servers yet, so don't 959 // return anything. 960 return kbfsmd.RevisionUninitialized, kbfsmd.RevisionUninitialized, 961 errNoFlushedRevisions 962 } 963 964 return jStatus.RevisionStart - 1, jStatus.RevisionEnd, nil 965 } 966 967 // getJournalPredecessorRevision returns the revision that precedes 968 // the current journal head if journaling enabled and there are 969 // unflushed MD updates; otherwise it returns 970 // kbfsmd.RevisionUninitialized. If there aren't any flushed MD 971 // revisions, it returns errNoFlushedRevisions. 972 func (fbo *folderBranchOps) getJournalPredecessorRevision(ctx context.Context) ( 973 kbfsmd.Revision, error) { 974 pred, _, err := fbo.getJournalRevisions(ctx) 975 return pred, err 976 } 977 978 // validateHeadLocked validates an untrusted head and sets it as trusted. 979 // see headTrustedState comment for more information. 980 func (fbo *folderBranchOps) validateHeadLocked( 981 ctx context.Context, lState *kbfssync.LockState, 982 md ImmutableRootMetadata) error { 983 fbo.headLock.AssertLocked(lState) 984 985 // Validate fbo against fetched md and discard the fetched one. 986 if fbo.head.TlfID() != md.TlfID() { 987 fbo.log.CCriticalf(ctx, "Fake untrusted TLF encountered %v %v %v %v", fbo.head.TlfID(), md.TlfID(), fbo.head.mdID, md.mdID) 988 return kbfsmd.MDTlfIDMismatch{CurrID: fbo.head.TlfID(), NextID: md.TlfID()} 989 } 990 fbo.headStatus = headTrusted 991 return nil 992 } 993 994 func (fbo *folderBranchOps) startMonitorChat(tlfName tlf.CanonicalName) { 995 if fbo.bType != standard || !fbo.config.Mode().TLFEditHistoryEnabled() { 996 return 997 } 998 999 fbo.editsLock.Lock() 1000 defer fbo.editsLock.Unlock() 1001 1002 fbo.launchEditMonitor.Do(func() { 1003 // The first event should initialize all the data. No need to 1004 // check the monitoring channel here; we already know 1005 // monitoring hasn't been started yet. 1006 fbo.editActivity.Add(1) 1007 fbo.editChannels <- editChannelActivity{nil, "", ""} 1008 fbo.goTracked(func() { fbo.monitorEditsChat(tlfName) }) 1009 }) 1010 } 1011 1012 var errNeedMDForPartialSyncConfig = errors.New( 1013 "needs MD for partial sync config") 1014 1015 func (fbo *folderBranchOps) getProtocolSyncConfig( 1016 ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata) ( 1017 ret keybase1.FolderSyncConfig, tlfPath string, err error) { 1018 fbo.syncLock.AssertAnyLocked(lState) 1019 1020 config := fbo.config.GetTlfSyncState(fbo.id()) 1021 ret.Mode = config.Mode 1022 if ret.Mode != keybase1.FolderSyncMode_PARTIAL { 1023 return ret, config.TlfPath, nil 1024 } 1025 1026 if kmd.TlfID() == tlf.NullID { 1027 return keybase1.FolderSyncConfig{}, "", errNeedMDForPartialSyncConfig 1028 } 1029 1030 var block *data.FileBlock 1031 // Skip block assembly if it's already cached. 1032 b, err := fbo.config.BlockCache().Get(config.Paths.Ptr) 1033 if err == nil { 1034 var ok bool 1035 block, ok = b.(*data.FileBlock) 1036 if !ok { 1037 return keybase1.FolderSyncConfig{}, "", errors.Errorf( 1038 "Partial sync block is not a file block, but %T", b) 1039 } 1040 } else { 1041 block = data.NewFileBlock().(*data.FileBlock) 1042 err = assembleBlockLocal( 1043 ctx, fbo.config.keyGetter(), fbo.config.Codec(), 1044 fbo.config.Crypto(), kmd, config.Paths.Ptr, block, 1045 config.Paths.Buf, config.Paths.ServerHalf) 1046 if err != nil { 1047 return keybase1.FolderSyncConfig{}, "", err 1048 } 1049 } 1050 1051 paths, err := syncPathListFromBlock(fbo.config.Codec(), block) 1052 if err != nil { 1053 return keybase1.FolderSyncConfig{}, "", err 1054 } 1055 ret.Paths = paths.Paths 1056 return ret, config.TlfPath, nil 1057 } 1058 1059 func (fbo *folderBranchOps) getProtocolSyncConfigUnlocked( 1060 ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata) ( 1061 ret keybase1.FolderSyncConfig, tlfPath string, err error) { 1062 fbo.syncLock.RLock(lState) 1063 defer fbo.syncLock.RUnlock(lState) 1064 return fbo.getProtocolSyncConfig(ctx, lState, kmd) 1065 } 1066 1067 func (fbo *folderBranchOps) syncOneNode( 1068 ctx context.Context, node Node, rmd ImmutableRootMetadata, 1069 priority int, action BlockRequestAction) (data.BlockPointer, error) { 1070 nodePath := fbo.nodeCache.PathFromNode(node) 1071 var b data.Block 1072 if node.EntryType() == data.Dir { 1073 b = data.NewDirBlock() 1074 } else { 1075 b = data.NewFileBlock() 1076 } 1077 ptr := nodePath.TailPointer() 1078 ch := fbo.config.BlockOps().BlockRetriever().Request( 1079 ctx, priority, rmd, ptr, b, data.TransientEntry, action) 1080 select { 1081 case err := <-ch: 1082 if err != nil { 1083 return data.ZeroPtr, err 1084 } 1085 return ptr, nil 1086 case <-ctx.Done(): 1087 return data.ZeroPtr, ctx.Err() 1088 } 1089 } 1090 1091 func (fbo *folderBranchOps) startOp( 1092 ctx context.Context, fs string, args ...interface{}) ( 1093 time.Time, *time.Timer) { 1094 now := fbo.config.Clock().Now() 1095 // Immediately log with vlog, and if the operation takes more than 1096 // one second, log via the normal logger too. 1097 fbo.deferVlog.CLogf(ctx, libkb.VLog1, fs, args...) 1098 timer := time.AfterFunc(1*time.Second, func() { 1099 select { 1100 case <-ctx.Done(): 1101 return 1102 default: 1103 newArgs := make([]interface{}, len(args)+1) 1104 newArgs[0] = now 1105 copy(newArgs[1:], args) 1106 fbo.deferLog.CDebugf( 1107 ctx, "(Long operation, started=%s) "+fs, newArgs...) 1108 } 1109 }) 1110 return now, timer 1111 } 1112 1113 func (fbo *folderBranchOps) endOp( 1114 ctx context.Context, startTime time.Time, timer *time.Timer, fs string, 1115 args ...interface{}) { 1116 timer.Stop() 1117 d := fbo.config.Clock().Now().Sub(startTime) 1118 newArgs := make([]interface{}, len(args)+1) 1119 newArgs[0] = d 1120 copy(newArgs[1:], args) 1121 fbo.defer2Log.CDebugf(ctx, "[duration=%s] "+fs, newArgs...) 1122 } 1123 1124 // doPartialSync iterates through the paths, deep-syncing them and 1125 // also syncing their parent directories up to the root node. 1126 func (fbo *folderBranchOps) doPartialSync( 1127 ctx context.Context, syncConfig keybase1.FolderSyncConfig, 1128 latestMerged ImmutableRootMetadata) (err error) { 1129 startTime, timer := fbo.startOp( 1130 ctx, "Starting partial sync at revision %d", latestMerged.Revision()) 1131 lState := makeFBOLockState() 1132 defer func() { 1133 fbo.endOp( 1134 ctx, startTime, timer, "Partial sync at revision %d done: %+v", 1135 latestMerged.Revision(), err) 1136 if err != nil { 1137 fbo.headLock.Lock(lState) 1138 if fbo.partialSyncConfig.Equal(syncConfig) && 1139 fbo.partialSyncRev == latestMerged.Revision() { 1140 fbo.partialSyncConfig = keybase1.FolderSyncConfig{} 1141 fbo.partialSyncRev = kbfsmd.RevisionUninitialized 1142 } 1143 fbo.headLock.Unlock(lState) 1144 } 1145 }() 1146 1147 var parentSyncAction, pathSyncAction BlockRequestAction 1148 var priority int 1149 switch syncConfig.Mode { 1150 case keybase1.FolderSyncMode_ENABLED: 1151 return errors.Errorf("Enabled mode passed to partial sync") 1152 case keybase1.FolderSyncMode_PARTIAL: 1153 // Use `PrefetchTail` for directories, to make sure that any child 1154 // blocks in the directory itself get prefetched. 1155 parentSyncAction = BlockRequestPrefetchTailWithSync 1156 pathSyncAction = BlockRequestWithDeepSync 1157 priority = defaultOnDemandRequestPriority - 1 1158 default: 1159 // For TLFs that aren't explicitly configured to be synced in 1160 // some way, use the working set cache. 1161 parentSyncAction = BlockRequestPrefetchTail 1162 // If we run out of space while prefetching the paths, just stop. 1163 pathSyncAction = BlockRequestPrefetchUntilFull 1164 // Don't flood outselves with prefetch requests when we're not 1165 // explicitly trying to sync the folder, just throttle them in 1166 // the background. 1167 priority = throttleRequestPriority 1168 } 1169 1170 rootNode, _, _, err := fbo.getRootNode(ctx) 1171 if err != nil { 1172 return err 1173 } 1174 _, err = fbo.syncOneNode( 1175 ctx, rootNode, latestMerged, priority, parentSyncAction) 1176 if err != nil { 1177 return err 1178 } 1179 1180 chs := make(map[string]<-chan struct{}, len(syncConfig.Paths)) 1181 // Look up and solo-sync each lead-up component of the path. 1182 pathLoop: 1183 for _, p := range syncConfig.Paths { 1184 select { 1185 case <-ctx.Done(): 1186 return ctx.Err() 1187 default: 1188 } 1189 fbo.vlog.CLogf(ctx, libkb.VLog1, "Partially-syncing %s", p) 1190 1191 parentPath, syncedElem := stdpath.Split(p) 1192 parents := strings.Split(strings.TrimSuffix(parentPath, "/"), "/") 1193 currNode := rootNode 1194 for _, parent := range parents { 1195 if len(parent) == 0 { 1196 continue 1197 } 1198 // TODO: parallelize the parent fetches and lookups. 1199 currNode, _, err = fbo.blocks.Lookup( 1200 ctx, lState, latestMerged.ReadOnly(), currNode, 1201 currNode.ChildName(parent)) 1202 switch errors.Cause(err).(type) { 1203 case idutil.NoSuchNameError: 1204 fbo.vlog.CLogf( 1205 ctx, libkb.VLog1, "Synced path %s doesn't exist yet", p) 1206 continue pathLoop 1207 case nil: 1208 default: 1209 return err 1210 } 1211 1212 if currNode == nil { 1213 // This can happen if an old bug (HOTPOT-616) kept a 1214 // deleted path in the history that has since been 1215 // changed into a symlink. 1216 fbo.vlog.CLogf( 1217 ctx, libkb.VLog1, "Ignoring symlink path %s", p) 1218 continue pathLoop 1219 } else if currNode.EntryType() != data.Dir { 1220 fbo.vlog.CLogf( 1221 ctx, libkb.VLog1, "Ignoring non-dir path %s (%s)", 1222 p, currNode.EntryType()) 1223 continue pathLoop 1224 } 1225 1226 // Use `PrefetchTail` for directories, to make sure that 1227 // any child blocks in the directory itself get 1228 // prefetched. 1229 _, err = fbo.syncOneNode( 1230 ctx, currNode, latestMerged, priority, parentSyncAction) 1231 if err != nil { 1232 return err 1233 } 1234 } 1235 1236 // Kick off a full deep sync of `syncedElem`. 1237 elemNode, _, err := fbo.blocks.Lookup( 1238 ctx, lState, latestMerged.ReadOnly(), currNode, 1239 currNode.ChildName(syncedElem)) 1240 switch errors.Cause(err).(type) { 1241 case idutil.NoSuchNameError: 1242 fbo.vlog.CLogf( 1243 ctx, libkb.VLog1, "Synced element %s doesn't exist yet", p) 1244 continue pathLoop 1245 case nil: 1246 default: 1247 return err 1248 } 1249 1250 if elemNode == nil { 1251 // This can happen if an old bug (HOTPOT-616) kept a 1252 // deleted path in the history that has since been changed 1253 // into a symlink. 1254 fbo.vlog.CLogf( 1255 ctx, libkb.VLog1, "Ignoring symlink path %s", p) 1256 continue pathLoop 1257 } 1258 1259 ptr, err := fbo.syncOneNode( 1260 ctx, elemNode, latestMerged, priority, pathSyncAction) 1261 if err != nil { 1262 return err 1263 } 1264 ch, err := fbo.config.BlockOps().Prefetcher(). 1265 WaitChannelForBlockPrefetch(ctx, ptr) 1266 if err != nil { 1267 return err 1268 } 1269 chs[p] = ch 1270 } 1271 1272 for p, ch := range chs { 1273 select { 1274 case <-ch: 1275 fbo.vlog.CLogf(ctx, libkb.VLog1, "Prefetch for %s complete", p) 1276 case <-ctx.Done(): 1277 return ctx.Err() 1278 } 1279 } 1280 return nil 1281 } 1282 1283 func (fbo *folderBranchOps) kickOffPartialSync( 1284 ctx context.Context, lState *kbfssync.LockState, 1285 syncConfig keybase1.FolderSyncConfig, rmd ImmutableRootMetadata) { 1286 if fbo.config.DiskBlockCache() == nil { 1287 return 1288 } 1289 1290 // Kick off a background partial sync. 1291 partialSyncCtx, cancel := context.WithCancel( 1292 fbo.ctxWithFBOID(context.Background())) 1293 fbo.log.CDebugf( 1294 ctx, "Partial sync with a new context: FBOID=%s", 1295 partialSyncCtx.Value(CtxFBOIDKey)) 1296 fbo.partialSyncs.Add(1) 1297 fbo.goTracked(func() { 1298 defer cancel() 1299 defer fbo.partialSyncs.Done() 1300 _ = fbo.doPartialSync(partialSyncCtx, syncConfig, rmd) 1301 }) 1302 1303 // Cancel the partial sync if the latest merged revision is updated. 1304 updatedCh := func() <-chan struct{} { 1305 fbo.headLock.Lock(lState) 1306 defer fbo.headLock.Unlock(lState) 1307 if rmd.Revision() != fbo.latestMergedRevision { 1308 fbo.vlog.CLogf( 1309 partialSyncCtx, libkb.VLog1, 1310 "Latest merged revision is now %d, not %d; "+ 1311 "aborting partial sync", fbo.latestMergedRevision, 1312 rmd.Revision()) 1313 return nil 1314 } else if rmd.Revision() <= fbo.partialSyncRev && 1315 fbo.partialSyncConfig.Equal(syncConfig) { 1316 fbo.vlog.CLogf( 1317 partialSyncCtx, libkb.VLog1, 1318 "Partial sync (mode=%s) already launched at revision %d; "+ 1319 "no need to run one for %d; aborting partial sync", 1320 syncConfig.Mode, fbo.partialSyncConfig, rmd.Revision()) 1321 return nil 1322 } 1323 fbo.partialSyncConfig = syncConfig 1324 fbo.partialSyncRev = rmd.Revision() 1325 return fbo.latestMergedUpdated 1326 }() 1327 if updatedCh == nil { 1328 cancel() 1329 } else { 1330 fbo.goTracked(func() { 1331 select { 1332 case <-updatedCh: 1333 cancel() 1334 case <-partialSyncCtx.Done(): 1335 } 1336 }) 1337 } 1338 1339 if syncConfig.Mode != keybase1.FolderSyncMode_PARTIAL { 1340 return 1341 } 1342 1343 // Kick off a mark-and-sweep for synced TLFs if one doesn't exist yet. 1344 fbo.syncLock.Lock(lState) 1345 defer fbo.syncLock.Unlock(lState) 1346 if fbo.markAndSweepTrigger == nil { 1347 trigger := make(chan struct{}, 1) 1348 fbo.markAndSweepTrigger = trigger 1349 fbo.goTracked(func() { fbo.partialMarkAndSweepLoop(trigger) }) 1350 } 1351 } 1352 1353 func (fbo *folderBranchOps) makeRecentFilesSyncConfig( 1354 ctx context.Context, rmd ImmutableRootMetadata) ( 1355 keybase1.FolderSyncConfig, error) { 1356 syncConfig := keybase1.FolderSyncConfig{ 1357 Mode: keybase1.FolderSyncMode_DISABLED, 1358 } 1359 h := rmd.GetTlfHandle() 1360 history := fbo.config.UserHistory().GetTlfHistory( 1361 h.GetCanonicalName(), fbo.id().Type()) 1362 pathsToSync := make(map[string]bool) 1363 for _, wh := range history.History { 1364 for _, e := range wh.Edits { 1365 parts := strings.SplitN(e.Filename, "/", 5) 1366 if len(parts) < 5 { 1367 continue 1368 } 1369 pathsToSync[parts[4]] = true 1370 } 1371 } 1372 for p := range pathsToSync { 1373 syncConfig.Paths = append(syncConfig.Paths, p) 1374 } 1375 return syncConfig, nil 1376 } 1377 1378 func (fbo *folderBranchOps) kickOffPartialSyncIfNeeded( 1379 ctx context.Context, lState *kbfssync.LockState, 1380 rmd ImmutableRootMetadata) { 1381 // Check if we need to kick off a partial sync. 1382 syncConfig, _, err := fbo.getProtocolSyncConfigUnlocked(ctx, lState, rmd) 1383 if err != nil { 1384 fbo.log.CDebugf(ctx, "Couldn't get sync config: %+v", err) 1385 return 1386 } 1387 1388 switch syncConfig.Mode { 1389 case keybase1.FolderSyncMode_ENABLED: 1390 // If fully syncing this TLF, the root block fetch is enable 1391 // to kick off the sync. 1392 return 1393 case keybase1.FolderSyncMode_DISABLED: 1394 // If we're not syncing the TLF at all, start a partial "sync" 1395 // using the recently-edited files list, storing the blocks in 1396 // the working set cache. 1397 if !fbo.config.Mode().TLFEditHistoryEnabled() || 1398 !fbo.config.Mode().EditHistoryPrefetchingEnabled() || 1399 fbo.config.Mode().DefaultBlockRequestAction() == BlockRequestSolo { 1400 return 1401 } 1402 err := fbo.editActivity.Wait(ctx) 1403 if err != nil { 1404 fbo.log.CDebugf(ctx, "Error waiting for edit activity: %+v", err) 1405 return 1406 } 1407 syncConfig, err = fbo.makeRecentFilesSyncConfig(ctx, rmd) 1408 if err != nil { 1409 fbo.log.CDebugf(ctx, 1410 "Error making recent files sync config: %+v", err) 1411 return 1412 } 1413 } 1414 1415 fbo.kickOffPartialSync(ctx, lState, syncConfig, rmd) 1416 } 1417 1418 func (fbo *folderBranchOps) markRecursive( 1419 ctx context.Context, lState *kbfssync.LockState, node Node, 1420 rmd ImmutableRootMetadata, tag string, cacheType DiskBlockCacheType) error { 1421 select { 1422 case <-ctx.Done(): 1423 return ctx.Err() 1424 default: 1425 } 1426 1427 err := fbo.blocks.MarkNode(ctx, lState, node, rmd, tag, cacheType) 1428 if err != nil { 1429 return err 1430 } 1431 1432 if node.EntryType() != data.Dir { 1433 return nil 1434 } 1435 1436 p := fbo.nodeCache.PathFromNode(node) 1437 children, err := fbo.blocks.GetChildren(ctx, lState, rmd, p) 1438 if err != nil { 1439 return err 1440 } 1441 for child := range children { 1442 childNode, _, err := fbo.Lookup(ctx, node, child) 1443 if err != nil { 1444 return err 1445 } 1446 if childNode == nil { 1447 // A symlink. 1448 continue 1449 } 1450 err = fbo.markRecursive(ctx, lState, childNode, rmd, tag, cacheType) 1451 if err != nil { 1452 return err 1453 } 1454 } 1455 return nil 1456 } 1457 1458 // doPartialMarkAndSweep runs a mark-and-sweep algorithm against all 1459 // the currently-synced paths, to delete any blocks not reachable from 1460 // one of these paths. 1461 func (fbo *folderBranchOps) doPartialMarkAndSweep( 1462 ctx context.Context, syncConfig keybase1.FolderSyncConfig, 1463 latestMerged ImmutableRootMetadata) (err error) { 1464 startTime, timer := fbo.startOp( 1465 ctx, "Starting partial mark-and-sweep at revision %d", 1466 latestMerged.Revision()) 1467 defer func() { 1468 fbo.endOp( 1469 ctx, startTime, timer, 1470 "Partial mark-and-sweep at revision %d done: %+v", 1471 latestMerged.Revision(), err) 1472 }() 1473 1474 if syncConfig.Mode != keybase1.FolderSyncMode_PARTIAL { 1475 return errors.Errorf( 1476 "Bad mode passed to partial unsync: %+v", syncConfig.Mode) 1477 } else if len(syncConfig.Paths) == 0 { 1478 return nil 1479 } 1480 1481 rootNode, _, _, err := fbo.getRootNode(ctx) 1482 if err != nil { 1483 return err 1484 } 1485 tag := ctx.Value(CtxFBOIDKey).(string) 1486 lState := makeFBOLockState() 1487 cacheType := DiskBlockSyncCache 1488 err = fbo.blocks.MarkNode( 1489 ctx, lState, rootNode, latestMerged, tag, cacheType) 1490 if err != nil { 1491 return err 1492 } 1493 1494 pathLoop: 1495 for _, p := range syncConfig.Paths { 1496 select { 1497 case <-ctx.Done(): 1498 return ctx.Err() 1499 default: 1500 } 1501 fbo.vlog.CLogf(ctx, libkb.VLog1, "Marking %s", p) 1502 1503 // Mark the parent directories. 1504 parentPath, syncedElem := stdpath.Split(p) 1505 parents := strings.Split(strings.TrimSuffix(parentPath, "/"), "/") 1506 currNode := rootNode 1507 for _, parent := range parents { 1508 if len(parent) == 0 { 1509 continue 1510 } 1511 // TODO: parallelize the parent fetches and lookups. 1512 currNode, _, err = fbo.Lookup( 1513 ctx, currNode, currNode.ChildName(parent)) 1514 switch errors.Cause(err).(type) { 1515 case idutil.NoSuchNameError: 1516 fbo.vlog.CLogf( 1517 ctx, libkb.VLog1, "Synced path %s doesn't exist yet", p) 1518 continue pathLoop 1519 case nil: 1520 default: 1521 return err 1522 } 1523 1524 err = fbo.blocks.MarkNode( 1525 ctx, lState, currNode, latestMerged, tag, cacheType) 1526 if err != nil { 1527 return err 1528 } 1529 } 1530 1531 // Now mark everything rooted at this path. 1532 currNode, _, err = fbo.Lookup( 1533 ctx, currNode, currNode.ChildName(syncedElem)) 1534 switch errors.Cause(err).(type) { 1535 case idutil.NoSuchNameError: 1536 fbo.vlog.CLogf( 1537 ctx, libkb.VLog1, "Synced element %s doesn't exist yet", p) 1538 continue pathLoop 1539 case nil: 1540 default: 1541 return err 1542 } 1543 1544 err = fbo.markRecursive( 1545 ctx, lState, currNode, latestMerged, tag, cacheType) 1546 if err != nil { 1547 return err 1548 } 1549 } 1550 1551 return fbo.config.DiskBlockCache().DeleteUnmarked( 1552 ctx, fbo.id(), tag, cacheType) 1553 } 1554 1555 func (fbo *folderBranchOps) kickOffPartialMarkAndSweep( 1556 ctx context.Context, lState *kbfssync.LockState, 1557 syncConfig keybase1.FolderSyncConfig, rmd ImmutableRootMetadata) ( 1558 <-chan struct{}, context.CancelFunc) { 1559 // Kick off a background mark-and-sweep. 1560 partialMSCtx, cancel := context.WithCancel( 1561 fbo.ctxWithFBOID(context.Background())) 1562 fbo.log.CDebugf( 1563 ctx, "Partial mark-and-sweep with a new context: FBOID=%s", 1564 partialMSCtx.Value(CtxFBOIDKey)) 1565 fbo.partialSyncs.Add(1) 1566 fbo.goTracked(func() { 1567 defer cancel() 1568 defer fbo.partialSyncs.Done() 1569 _ = fbo.doPartialMarkAndSweep(partialMSCtx, syncConfig, rmd) 1570 }) 1571 1572 // Cancel the partial sync if the latest merged revision is updated. 1573 updatedCh := func() <-chan struct{} { 1574 fbo.headLock.Lock(lState) 1575 defer fbo.headLock.Unlock(lState) 1576 if rmd.Revision() != fbo.latestMergedRevision { 1577 fbo.vlog.CLogf( 1578 partialMSCtx, libkb.VLog1, 1579 "Latest merged changed is now %d, not %d; "+ 1580 "aborting partial mark-and-sweep", fbo.latestMergedRevision, 1581 rmd.Revision()) 1582 return nil 1583 } 1584 return fbo.latestMergedUpdated 1585 }() 1586 if updatedCh == nil { 1587 cancel() 1588 } else { 1589 fbo.goTracked(func() { 1590 select { 1591 case <-updatedCh: 1592 cancel() 1593 case <-partialMSCtx.Done(): 1594 } 1595 }) 1596 } 1597 return partialMSCtx.Done(), cancel 1598 } 1599 1600 func (fbo *folderBranchOps) kickOffPartialMarkAndSweepIfNeeded( 1601 ctx context.Context, lState *kbfssync.LockState, triggered bool, 1602 lastMDRev kbfsmd.Revision) ( 1603 <-chan struct{}, context.CancelFunc, kbfsmd.Revision, error) { 1604 if triggered { 1605 defer fbo.partialSyncs.Done() 1606 } 1607 1608 md, err := fbo.getLatestMergedMD(ctx, lState) 1609 if err != nil { 1610 fbo.log.CDebugf(ctx, "Couldn't get latest merged MD: %+v", err) 1611 return nil, nil, 0, nil 1612 } 1613 if md == (ImmutableRootMetadata{}) || 1614 md.Revision() == kbfsmd.RevisionUninitialized { 1615 return nil, nil, 0, errors.New("Unexpectedly no merged revision") 1616 } 1617 1618 // Skip mark-and-sweep if we were woken up by the timer and 1619 // the revision hasn't changed since last time. 1620 if !triggered && md.Revision() == lastMDRev { 1621 fbo.vlog.CLogf( 1622 ctx, libkb.VLog1, 1623 "Revision hasn't changed since last mark-and-sweep") 1624 return nil, nil, 0, nil 1625 } 1626 1627 syncConfig, _, err := fbo.getProtocolSyncConfigUnlocked(ctx, lState, md) 1628 if err != nil { 1629 return nil, nil, 0, err 1630 } 1631 if syncConfig.Mode != keybase1.FolderSyncMode_PARTIAL { 1632 return nil, nil, 0, errors.New("No partial sync config") 1633 } 1634 1635 // Kick off the mark-and-sweep, and wait for it to finish or 1636 // be pre-empted. 1637 currMarkAndSweepCtxDone, currMarkAndSweepCancel := 1638 fbo.kickOffPartialMarkAndSweep(ctx, lState, syncConfig, md) 1639 return currMarkAndSweepCtxDone, currMarkAndSweepCancel, md.Revision(), nil 1640 } 1641 1642 func (fbo *folderBranchOps) partialMarkAndSweepLoop(trigger <-chan struct{}) { 1643 // For partially-synced TLFs, run this: 1644 // * Once an hour-ish, only if the latest merged revision has changed. 1645 // * When a path is removed from the config. 1646 // 1647 // Cancel the running mark-and-sweep when: 1648 // * The latest merged revision changes. 1649 // * The config changes. 1650 // 1651 // Exit this loop: 1652 // * On shutdown. 1653 // * When no longer configured to be partially syncing. 1654 ctx, cancel := context.WithCancel(fbo.ctxWithFBOID(context.Background())) 1655 defer cancel() 1656 fbo.log.CDebugf(ctx, "Starting mark-and-sweep loop") 1657 1658 // Set the first timer to be some random duration less than the 1659 // period, to spread out the work of different TLFs. 1660 d := time.Duration(rand.Int63n(int64(markAndSweepPeriod))) 1661 timer := time.NewTimer(d) 1662 1663 var currMarkAndSweepCtxDone <-chan struct{} 1664 var currMarkAndSweepCancel context.CancelFunc 1665 defer func() { 1666 if currMarkAndSweepCancel != nil { 1667 currMarkAndSweepCancel() 1668 } 1669 timer.Stop() 1670 }() 1671 1672 lastMDRev := kbfsmd.RevisionUninitialized 1673 1674 fbo.log.CDebugf(ctx, "Scheduling first timer for %s", d) 1675 lState := makeFBOLockState() 1676 for { 1677 triggered := false 1678 select { 1679 case <-currMarkAndSweepCtxDone: 1680 fbo.vlog.CLogf( 1681 ctx, libkb.VLog1, "Mark-and-sweep finished; resetting timer") 1682 timer = time.NewTimer(markAndSweepPeriod) 1683 currMarkAndSweepCtxDone = nil 1684 continue 1685 case _, ok := <-trigger: 1686 if !ok { 1687 fbo.log.CDebugf(ctx, "Mark-and-sweep is shutting down") 1688 return 1689 } 1690 fbo.vlog.CLogf(ctx, libkb.VLog1, "New mark-and-sweep triggered") 1691 triggered = true 1692 case <-timer.C: 1693 fbo.vlog.CLogf(ctx, libkb.VLog1, "Mark-and-sweep timer fired") 1694 case <-fbo.shutdownChan: 1695 fbo.vlog.CLogf(ctx, libkb.VLog1, "Shutdown") 1696 return 1697 } 1698 1699 if currMarkAndSweepCancel != nil { 1700 currMarkAndSweepCancel() 1701 } 1702 timer.Stop() 1703 1704 // Kick off the mark-and-sweep, and wait for it to finish or 1705 // be pre-empted. 1706 done, cancel, rev, err := fbo.kickOffPartialMarkAndSweepIfNeeded( 1707 ctx, lState, triggered, lastMDRev) 1708 if err != nil { 1709 return 1710 } 1711 if rev == 0 { 1712 fbo.vlog.CLogf( 1713 ctx, libkb.VLog1, 1714 "No mark-and-sweep was launched; resetting timer") 1715 timer = time.NewTimer(markAndSweepPeriod) 1716 continue 1717 } 1718 currMarkAndSweepCtxDone, currMarkAndSweepCancel = done, cancel 1719 lastMDRev = rev 1720 } 1721 } 1722 1723 func (fbo *folderBranchOps) isSyncedTlf() bool { 1724 return fbo.branch() == data.MasterBranch && fbo.config.IsSyncedTlf(fbo.id()) 1725 } 1726 1727 func (fbo *folderBranchOps) kickOffRootBlockFetch( 1728 ctx context.Context, rmd ImmutableRootMetadata) <-chan error { 1729 ptr := rmd.Data().Dir.BlockPointer 1730 action := fbo.config.Mode().DefaultBlockRequestAction(). 1731 AddStopPrefetchIfFull() 1732 if !action.prefetch() && fbo.isSyncedTlf() { 1733 // Explicitly add the prefetch action for synced folders when 1734 // getting the root block, since in some modes (like 1735 // constrained) the prefetch action isn't set by default. 1736 action = action.AddPrefetch() 1737 } 1738 if fbo.branch() != data.MasterBranch { 1739 action = action.AddNonMasterBranch() 1740 } 1741 1742 return fbo.config.BlockOps().BlockRetriever().Request( 1743 ctx, defaultOnDemandRequestPriority-1, rmd, ptr, data.NewDirBlock(), 1744 data.TransientEntry, action) 1745 } 1746 1747 func (fbo *folderBranchOps) waitForRootBlockFetchAndSyncIfNeeded( 1748 ctx context.Context, rmd ImmutableRootMetadata, rootCh <-chan error, 1749 updatedCh <-chan struct{}) ( 1750 rootPtr data.BlockPointer, waitCh <-chan struct{}, err error) { 1751 rev := rmd.Revision() 1752 select { 1753 case err := <-rootCh: 1754 if err != nil { 1755 fbo.log.CDebugf(ctx, "Error getting root block: %+v", err) 1756 return data.ZeroPtr, nil, err 1757 } 1758 case <-updatedCh: 1759 fbo.vlog.CLogf( 1760 ctx, libkb.VLog1, "The latest merged rev has been updated") 1761 return data.ZeroPtr, nil, nil 1762 case <-fbo.shutdownChan: 1763 fbo.log.CDebugf(ctx, "Shutdown, canceling root block wait") 1764 return data.ZeroPtr, nil, errors.WithStack(data.ShutdownHappenedError{}) 1765 case <-ctx.Done(): 1766 fbo.log.CDebugf(ctx, "Context canceled, canceling root block wait") 1767 return data.ZeroPtr, nil, errors.WithStack(ctx.Err()) 1768 } 1769 1770 rootPtr = rmd.Data().Dir.BlockPointer 1771 fbo.vlog.CLogf( 1772 ctx, libkb.VLog1, "Waiting for prefetch of revision %d, ptr %v", 1773 rev, rootPtr) 1774 waitCh, err = fbo.config.BlockOps().Prefetcher(). 1775 WaitChannelForBlockPrefetch(ctx, rootPtr) 1776 if err != nil { 1777 fbo.log.CDebugf(ctx, 1778 "Error getting wait channel for prefetch: %+v", err) 1779 return data.ZeroPtr, nil, err 1780 } 1781 1782 if fbo.isSyncedTlf() { 1783 fbo.syncedTlfObservers.fullSyncStarted( 1784 ctx, fbo.id(), rmd.Revision(), waitCh) 1785 } 1786 return rootPtr, waitCh, nil 1787 } 1788 1789 func (fbo *folderBranchOps) kickOffRootBlockFetchAndWait( 1790 ctx context.Context, rmd ImmutableRootMetadata, updatedCh <-chan struct{}) ( 1791 rootPtr data.BlockPointer, waitCh <-chan struct{}, err error) { 1792 rootPtr = rmd.Data().Dir.BlockPointer 1793 rev := rmd.Revision() 1794 fbo.vlog.CLogf( 1795 ctx, libkb.VLog1, 1796 "Fetching root block of revision %d, ptr %v, and syncing", rev, rootPtr) 1797 rootCh := fbo.kickOffRootBlockFetch(ctx, rmd) 1798 return fbo.waitForRootBlockFetchAndSyncIfNeeded(ctx, rmd, rootCh, updatedCh) 1799 } 1800 1801 func (fbo *folderBranchOps) kickOffRootBlockFetchAndSyncInBackground( 1802 ctx context.Context, rmd ImmutableRootMetadata, updatedCh <-chan struct{}) { 1803 rev := rmd.Revision() 1804 fbo.vlog.CLogf( 1805 ctx, libkb.VLog1, 1806 "Fetching root block of revision %d, ptr %v", rev, 1807 rmd.Data().Dir.BlockPointer) 1808 rootCh := fbo.kickOffRootBlockFetch(ctx, rmd) 1809 fbo.rootWaits.Add(1) 1810 fbo.goTracked(func() { 1811 defer fbo.rootWaits.Done() 1812 ctx, cancelFunc := fbo.newCtxWithFBOID() 1813 defer cancelFunc() 1814 _, _, _ = fbo.waitForRootBlockFetchAndSyncIfNeeded( 1815 ctx, rmd, rootCh, updatedCh) 1816 }) 1817 } 1818 1819 func (fbo *folderBranchOps) commitFlushedMD( 1820 rmd ImmutableRootMetadata, updatedCh <-chan struct{}) { 1821 diskMDCache := fbo.config.DiskMDCache() 1822 if diskMDCache == nil { 1823 return 1824 } 1825 1826 // Bail out if the latest merged revision has already been updated. 1827 select { 1828 case <-updatedCh: 1829 return 1830 default: 1831 } 1832 1833 ctx := fbo.ctxWithFBOID(context.Background()) 1834 rev := rmd.Revision() 1835 syncConfig := fbo.config.GetTlfSyncState(fbo.id()) 1836 switch syncConfig.Mode { 1837 case keybase1.FolderSyncMode_ENABLED: 1838 // For synced TLFs, wait for prefetching to complete for 1839 // `rootPtr`. When it's successfully done, commit the 1840 // corresponding MD. 1841 rootPtr, waitCh, err := fbo.kickOffRootBlockFetchAndWait( 1842 ctx, rmd, updatedCh) 1843 if err != nil { 1844 return 1845 } 1846 1847 select { 1848 case <-waitCh: 1849 case <-updatedCh: 1850 fbo.vlog.CLogf( 1851 ctx, libkb.VLog1, "The latest merged rev has been updated") 1852 fbo.config.BlockOps().Prefetcher().CancelPrefetch(rootPtr) 1853 return 1854 case <-fbo.shutdownChan: 1855 fbo.log.CDebugf(ctx, "Shutdown, canceling prefetch wait") 1856 return 1857 } 1858 1859 prefetchStatus := fbo.config.PrefetchStatus(ctx, fbo.id(), rootPtr) 1860 if prefetchStatus != FinishedPrefetch { 1861 fbo.vlog.CLogf( 1862 ctx, libkb.VLog1, 1863 "Revision was not fully prefetched: status=%s", prefetchStatus) 1864 return 1865 } 1866 1867 fbo.vlog.CLogf( 1868 ctx, libkb.VLog1, "Prefetch for revision %d complete; commiting", 1869 rev) 1870 case keybase1.FolderSyncMode_PARTIAL: 1871 // For partially-synced TLFs, wait for the partial sync to 1872 // complete, or for an update to happen. 1873 lState := makeFBOLockState() 1874 fbo.kickOffPartialSyncIfNeeded(ctx, lState, rmd) 1875 ctx, cancel := context.WithCancel(ctx) 1876 defer cancel() 1877 fbo.goTracked(func() { 1878 select { 1879 case <-updatedCh: 1880 cancel() 1881 case <-fbo.shutdownChan: 1882 cancel() 1883 case <-ctx.Done(): 1884 } 1885 }) 1886 err := fbo.partialSyncs.Wait(ctx) 1887 if err != nil { 1888 fbo.log.CDebugf(ctx, "Error waiting for partial sync: %+v", err) 1889 } 1890 } 1891 1892 err := diskMDCache.Commit(ctx, fbo.id(), rev) 1893 if err != nil { 1894 fbo.log.CDebugf(ctx, "Error commiting revision %d: %+v", rev, err) 1895 } 1896 } 1897 1898 func (fbo *folderBranchOps) setObfuscatorSecret( 1899 ctx context.Context, kmd libkey.KeyMetadata) error { 1900 if !fbo.config.Mode().DoLogObfuscation() { 1901 return nil 1902 } 1903 1904 fbo.obLock.Lock() 1905 defer fbo.obLock.Unlock() 1906 1907 if fbo.obSecret != nil { 1908 panic("Obfuscator secret is being set more than once") 1909 } 1910 1911 fbo.log.CDebugf(ctx, "Making the log obfuscator secret") 1912 secret, err := getMDObfuscationSecret(ctx, fbo.config.KeyManager(), kmd) 1913 if err != nil { 1914 return err 1915 } 1916 fbo.obSecret = secret 1917 return nil 1918 } 1919 1920 func (fbo *folderBranchOps) makeObfuscator() data.Obfuscator { 1921 fbo.obLock.RLock() 1922 defer fbo.obLock.RUnlock() 1923 return makeMDObfuscatorFromSecret(fbo.obSecret, fbo.config.Mode()) 1924 } 1925 1926 func (fbo *folderBranchOps) setHeadLocked( 1927 ctx context.Context, lState *kbfssync.LockState, 1928 md ImmutableRootMetadata, headStatus headTrustStatus, 1929 ct mdCommitType) (err error) { 1930 fbo.mdWriterLock.AssertLocked(lState) 1931 fbo.headLock.AssertLocked(lState) 1932 1933 isFirstHead := fbo.head == ImmutableRootMetadata{} 1934 wasReadable := false 1935 if isFirstHead { 1936 err = fbo.setObfuscatorSecret(ctx, md.ReadOnly()) 1937 if err != nil { 1938 return err 1939 } 1940 defer func() { 1941 if err != nil && fbo.head == (ImmutableRootMetadata{}) { 1942 // If we didn't successfully set the head, we need to 1943 // unset the secret. 1944 fbo.obLock.Lock() 1945 defer fbo.obLock.Unlock() 1946 fbo.obSecret = nil 1947 } 1948 }() 1949 } else { 1950 if headStatus == headUntrusted { 1951 panic("setHeadLocked: Trying to set an untrusted head over an existing head") 1952 } 1953 1954 wasReadable = fbo.head.IsReadable() 1955 1956 if fbo.headStatus == headUntrusted { 1957 err := fbo.validateHeadLocked(ctx, lState, md) 1958 if err != nil { 1959 return err 1960 } 1961 if fbo.head.mdID == md.mdID { 1962 return nil 1963 } 1964 } 1965 1966 if fbo.head.mdID == md.mdID { 1967 panic(errors.Errorf("Re-putting the same MD: %s", md.mdID)) 1968 } 1969 } 1970 1971 fbo.log.CDebugf(ctx, "Setting head revision to %d", md.Revision()) 1972 1973 // If this is the first time the MD is being set, and we are 1974 // operating on unmerged data, initialize the state properly and 1975 // kick off conflict resolution. 1976 if isFirstHead && md.MergedStatus() == kbfsmd.Unmerged { 1977 fbo.setBranchIDLocked(lState, md.BID()) 1978 1979 // Set the unflushed edit history. 1980 _, unmergedMDs, err := getUnmergedMDUpdates( 1981 ctx, fbo.config, fbo.id(), md.BID(), md.Revision()) 1982 if err != nil { 1983 fbo.log.CDebugf(ctx, "Couldn't get unmerged MDs: %+v", err) 1984 return err 1985 } 1986 for _, unmergedMD := range unmergedMDs { 1987 err = fbo.handleUnflushedEditNotifications(ctx, unmergedMD) 1988 if err != nil { 1989 fbo.log.CDebugf(ctx, 1990 "Couldn't get unflushed edits for %d: %+v", 1991 unmergedMD.Revision(), err) 1992 return err 1993 } 1994 } 1995 1996 // Use uninitialized for the merged branch; the unmerged 1997 // revision is enough to trigger conflict resolution. 1998 fbo.cr.Resolve(ctx, md.Revision(), kbfsmd.RevisionUninitialized) 1999 } else if md.MergedStatus() == kbfsmd.Merged { 2000 journalEnabled := TLFJournalEnabled(fbo.config, fbo.id()) 2001 if journalEnabled { 2002 if isFirstHead { 2003 // If journaling is on, and this is the first head 2004 // we're setting, we have to make sure we use the 2005 // server's notion of the latest MD, not the one 2006 // potentially coming from our journal. If there are 2007 // no flushed revisions, it's not a hard error, and we 2008 // just leave the latest merged revision 2009 // uninitialized. 2010 journalPred, err := fbo.getJournalPredecessorRevision(ctx) 2011 switch err { 2012 case nil: 2013 // journalPred will be 2014 // kbfsmd.RevisionUninitialized when the journal 2015 // is empty. 2016 if journalPred >= kbfsmd.RevisionInitial { 2017 fbo.setLatestMergedRevisionLocked( 2018 ctx, lState, journalPred, false) 2019 2020 // Set the unflushed edit history. 2021 mds, err := getMergedMDUpdates( 2022 ctx, fbo.config, fbo.id(), journalPred+1, nil) 2023 if err != nil { 2024 fbo.log.CDebugf(ctx, 2025 "Couldn't get journal MDs: %+v", err) 2026 return err 2027 } 2028 for _, mergedMD := range mds { 2029 err = fbo.handleUnflushedEditNotifications( 2030 ctx, mergedMD) 2031 if err != nil { 2032 fbo.log.CDebugf(ctx, 2033 "Couldn't get unflushed edits for %d: %+v", 2034 mergedMD.Revision(), err) 2035 return err 2036 } 2037 } 2038 } else { 2039 fbo.setLatestMergedRevisionLocked(ctx, lState, 2040 md.Revision(), false) 2041 } 2042 case errNoFlushedRevisions: 2043 // The server has no revisions, so leave the 2044 // latest merged revision uninitialized. 2045 default: 2046 return err 2047 } 2048 } else if md.putToServer { 2049 // If this isn't the first head, then this is either 2050 // an update from the server, or an update just 2051 // written by the client. But since journaling is on, 2052 // then latter case will be handled by onMDFlush when 2053 // the update is properly flushed to the server. So 2054 // ignore updates that haven't yet been put to the 2055 // server. 2056 fbo.setLatestMergedRevisionLocked( 2057 ctx, lState, md.Revision(), false) 2058 } 2059 } else { 2060 // This is a merged revision, and journaling is disabled, 2061 // so it's definitely the latest revision on the server as 2062 // well. 2063 fbo.setLatestMergedRevisionLocked(ctx, lState, md.Revision(), false) 2064 } 2065 } 2066 2067 if ct == mdCommit { 2068 latestMergedUpdated := fbo.latestMergedUpdated 2069 fbo.goTracked(func() { fbo.commitFlushedMD(md, latestMergedUpdated) }) 2070 } 2071 2072 // Make sure that any unembedded block changes have been swapped 2073 // back in. 2074 if fbo.config.Mode().BlockManagementEnabled() && 2075 md.data.Changes.Info.BlockPointer != data.ZeroPtr && 2076 len(md.data.Changes.Ops) == 0 { 2077 return errors.New("Must swap in block changes before setting head") 2078 } 2079 2080 fbo.head = md 2081 if isFirstHead && headStatus == headTrusted { 2082 fbo.headStatus = headTrusted 2083 } 2084 fbo.status.setRootMetadata(md) 2085 if isFirstHead { 2086 // Start registering for updates right away, using this MD 2087 // as a starting point. Only standard FBOs get updates. 2088 if fbo.bType == standard { 2089 if fbo.config.Mode().TLFUpdatesEnabled() { 2090 fbo.updateDoneChan = make(chan struct{}) 2091 fbo.goTracked(fbo.registerAndWaitForUpdates) 2092 } 2093 fbo.startMonitorChat(md.GetTlfHandle().GetCanonicalName()) 2094 } 2095 2096 // If journaling is enabled, we should make sure to enable it 2097 // for this TLF. That's because we may have received the TLF 2098 // ID from the service, rather than via a GetIDForHandle call, 2099 // and so we might have skipped the journal. 2100 if jManager, err := GetJournalManager(fbo.config); err == nil { 2101 _, _ = jManager.getTLFJournal(fbo.id(), md.GetTlfHandle()) 2102 } 2103 } 2104 if !wasReadable && md.IsReadable() { 2105 // Let any listeners know that this folder is now readable, 2106 // which may indicate that a rekey successfully took place. 2107 fbo.config.Reporter().Notify(ctx, mdReadSuccessNotification( 2108 md.GetTlfHandle(), md.TlfID().Type() == tlf.Public)) 2109 } 2110 return nil 2111 } 2112 2113 func mdToCommitType(md ImmutableRootMetadata) mdCommitType { 2114 if md.putToServer { 2115 return mdCommit 2116 } 2117 return mdNoCommit 2118 } 2119 2120 // setNewInitialHeadLocked is for when we're creating a brand-new TLF. 2121 // This is trusted. 2122 func (fbo *folderBranchOps) setNewInitialHeadLocked(ctx context.Context, 2123 lState *kbfssync.LockState, md ImmutableRootMetadata) error { 2124 fbo.mdWriterLock.AssertLocked(lState) 2125 fbo.headLock.AssertLocked(lState) 2126 if fbo.head != (ImmutableRootMetadata{}) { 2127 return errors.New("Unexpected non-nil head in setNewInitialHeadLocked") 2128 } 2129 if md.Revision() != kbfsmd.RevisionInitial { 2130 return errors.Errorf("setNewInitialHeadLocked unexpectedly called with revision %d", md.Revision()) 2131 } 2132 return fbo.setHeadLocked(ctx, lState, md, headTrusted, mdToCommitType(md)) 2133 } 2134 2135 // setInitialHeadTrustedLocked is for when the given RootMetadata 2136 // was fetched due to a user action, and will be checked against the 2137 // TLF name. 2138 func (fbo *folderBranchOps) setInitialHeadTrustedLocked(ctx context.Context, 2139 lState *kbfssync.LockState, md ImmutableRootMetadata, 2140 ct mdCommitType) error { 2141 fbo.mdWriterLock.AssertLocked(lState) 2142 fbo.headLock.AssertLocked(lState) 2143 if fbo.head != (ImmutableRootMetadata{}) { 2144 return errors.New("Unexpected non-nil head in setInitialHeadTrustedLocked") 2145 } 2146 return fbo.setHeadLocked(ctx, lState, md, headTrusted, ct) 2147 } 2148 2149 // setHeadSuccessorLocked is for when we're applying updates from the 2150 // server or when we're applying new updates we created ourselves. 2151 func (fbo *folderBranchOps) setHeadSuccessorLocked(ctx context.Context, 2152 lState *kbfssync.LockState, md ImmutableRootMetadata, rebased bool) error { 2153 fbo.mdWriterLock.AssertLocked(lState) 2154 fbo.headLock.AssertLocked(lState) 2155 if fbo.head == (ImmutableRootMetadata{}) { 2156 // This can happen in tests via SyncFromServer(). 2157 return fbo.setInitialHeadTrustedLocked( 2158 ctx, lState, md, mdToCommitType(md)) 2159 } 2160 2161 if !rebased { 2162 err := fbo.head.CheckValidSuccessor(fbo.head.mdID, md.ReadOnly()) 2163 if err != nil { 2164 return err 2165 } 2166 } 2167 2168 oldHandle := fbo.head.GetTlfHandle() 2169 newHandle := md.GetTlfHandle() 2170 2171 // Newer handles should be equal or more resolved over time. 2172 // 2173 // TODO: In some cases, they shouldn't, e.g. if we're on an 2174 // unmerged branch. Add checks for this. 2175 resolvesTo, partialResolvedOldHandle, err := 2176 oldHandle.ResolvesTo( 2177 ctx, fbo.config.Codec(), fbo.config.KBPKI(), 2178 tlfhandle.ConstIDGetter{ID: fbo.id()}, fbo.config, *newHandle) 2179 if err != nil { 2180 fbo.log.CDebugf(ctx, "oldHandle=%+v, newHandle=%+v: err=%+v", oldHandle, newHandle, err) 2181 return err 2182 } 2183 2184 oldName := oldHandle.GetCanonicalName() 2185 newName := newHandle.GetCanonicalName() 2186 2187 if !resolvesTo { 2188 fbo.log.CDebugf(ctx, "Incompatible handle error, "+ 2189 "oldHandle: %#v, partialResolvedOldHandle: %#v, newHandle: %#v", 2190 oldHandle, partialResolvedOldHandle, newHandle) 2191 return IncompatibleHandleError{ 2192 oldName, 2193 partialResolvedOldHandle.GetCanonicalName(), 2194 newName, 2195 } 2196 } 2197 2198 err = fbo.setHeadLocked(ctx, lState, md, headTrusted, mdToCommitType(md)) 2199 if err != nil { 2200 return err 2201 } 2202 2203 if oldName != newName { 2204 fbo.log.CDebugf(ctx, "Handle changed (%s -> %s)", 2205 oldName, newName) 2206 2207 fbo.config.MDCache().ChangeHandleForID(oldHandle, newHandle) 2208 // If the handle has changed, send out a notification. 2209 fbo.observers.tlfHandleChange(ctx, fbo.head.GetTlfHandle()) 2210 // Also the folder should be re-identified given the 2211 // newly-resolved assertions. 2212 func() { 2213 fbo.identifyLock.Lock() 2214 defer fbo.identifyLock.Unlock() 2215 fbo.identifyDone = false 2216 }() 2217 } 2218 2219 return nil 2220 } 2221 2222 // setHeadPredecessorLocked is for when we're unstaging updates. 2223 func (fbo *folderBranchOps) setHeadPredecessorLocked(ctx context.Context, 2224 lState *kbfssync.LockState, md ImmutableRootMetadata) error { 2225 fbo.mdWriterLock.AssertLocked(lState) 2226 fbo.headLock.AssertLocked(lState) 2227 if fbo.head == (ImmutableRootMetadata{}) { 2228 return errors.New("Unexpected nil head in setHeadPredecessorLocked") 2229 } 2230 if fbo.head.Revision() <= kbfsmd.RevisionInitial { 2231 return errors.Errorf("setHeadPredecessorLocked unexpectedly called with revision %d", fbo.head.Revision()) 2232 } 2233 2234 // Allow merged writes to be walked back, as long as they're 2235 // larger than the latest merged revision on the server. 2236 if fbo.head.MergedStatus() == kbfsmd.Merged && 2237 fbo.head.Revision() <= fbo.latestMergedRevision { 2238 return errors.New("Unexpected merged head in setHeadPredecessorLocked") 2239 } 2240 2241 err := md.CheckValidSuccessor(md.mdID, fbo.head.ReadOnly()) 2242 if err != nil { 2243 return err 2244 } 2245 2246 oldHandle := fbo.head.GetTlfHandle() 2247 newHandle := md.GetTlfHandle() 2248 2249 // The two handles must be the same, since no rekeying is done 2250 // while unmerged. 2251 2252 eq, err := oldHandle.Equals(fbo.config.Codec(), *newHandle) 2253 if err != nil { 2254 return err 2255 } 2256 if !eq { 2257 return errors.Errorf( 2258 "head handle %v unexpectedly not equal to new handle = %v", 2259 oldHandle, newHandle) 2260 } 2261 2262 return fbo.setHeadLocked(ctx, lState, md, headTrusted, mdToCommitType(md)) 2263 } 2264 2265 // setHeadConflictResolvedLocked is for when we're setting the merged 2266 // update with resolved conflicts. 2267 func (fbo *folderBranchOps) setHeadConflictResolvedLocked(ctx context.Context, 2268 lState *kbfssync.LockState, md ImmutableRootMetadata) error { 2269 fbo.mdWriterLock.AssertLocked(lState) 2270 fbo.headLock.AssertLocked(lState) 2271 if fbo.head.MergedStatus() != kbfsmd.Unmerged { 2272 return errors.New("Unexpected merged head in setHeadConflictResolvedLocked") 2273 } 2274 if md.MergedStatus() != kbfsmd.Merged { 2275 return errors.New("Unexpected unmerged update in setHeadConflictResolvedLocked") 2276 } 2277 2278 return fbo.setHeadLocked(ctx, lState, md, headTrusted, mdToCommitType(md)) 2279 } 2280 2281 func (fbo *folderBranchOps) identifyOnce( 2282 ctx context.Context, md ReadOnlyRootMetadata) error { 2283 fbo.identifyLock.Lock() 2284 defer fbo.identifyLock.Unlock() 2285 2286 ei := tlfhandle.GetExtendedIdentify(ctx) 2287 if !ei.Behavior.AlwaysRunIdentify() { 2288 if fbo.identifyDone || 2289 (fbo.identifyDoneWithWarning && 2290 ei.Behavior.WarningInsteadOfErrorOnBrokenTracks()) { 2291 // TODO: provide a way for the service to break this cache 2292 // when identify state changes on a TLF. For now, we do it 2293 // this way to make chat work. 2294 return nil 2295 } 2296 } 2297 2298 h := md.GetTlfHandle() 2299 fbo.log.CDebugf(ctx, "Running identifies on %s", h.GetCanonicalPath()) 2300 kbpki := fbo.config.KBPKI() 2301 err := tlfhandle.IdentifyHandle(ctx, kbpki, kbpki, fbo.config, h) 2302 if err != nil { 2303 fbo.log.CDebugf(ctx, "Identify finished with error: %v", err) 2304 // For now, if the identify fails, let the 2305 // next function to hit this code path retry. 2306 return err 2307 } 2308 2309 switch { 2310 case ei.Behavior.WarningInsteadOfErrorOnBrokenTracks() && 2311 len(ei.GetTlfBreakAndClose().Breaks) > 0: 2312 // In the (currently unused) condition that we get here when 2313 // `ei.Behavior.AlwaysRunIdentify()` is true, avoid setting 2314 // multiple timers. 2315 if fbo.identifyDoneWithWarning { 2316 break 2317 } 2318 2319 // In this case, the caller has explicitly requested that we 2320 // treat proof failures as warnings, instead of errors. For 2321 // example, the GUI does this and shows a warning banner but 2322 // still allows access to the files. Identifies are 2323 // expensive, so in this case we skip future identifies that 2324 // also have this behavior for a short time period. 2325 fbo.log.CDebugf(ctx, 2326 "Identify finished with no error but broken proof warnings; "+ 2327 "caching result for %d", cacheBrokenProofIdentifiesDuration) 2328 fbo.identifyDoneWithWarning = true 2329 timer := time.NewTimer(cacheBrokenProofIdentifiesDuration) 2330 fbo.goTracked(func() { 2331 select { 2332 case <-timer.C: 2333 fbo.identifyLock.Lock() 2334 defer fbo.identifyLock.Unlock() 2335 fbo.vlog.CLogf( 2336 context.TODO(), libkb.VLog1, 2337 "Expiring cached identify with broken proofs") 2338 fbo.identifyDoneWithWarning = false 2339 case <-fbo.shutdownChan: 2340 timer.Stop() 2341 } 2342 }) 2343 2344 case ei.Behavior == keybase1.TLFIdentifyBehavior_CHAT_SKIP: 2345 fbo.log.CDebugf(ctx, "Identify skipped") 2346 default: 2347 fbo.log.CDebugf(ctx, "Identify finished successfully") 2348 fbo.identifyDone = true 2349 fbo.identifyTime = fbo.config.Clock().Now() 2350 } 2351 return nil 2352 } 2353 2354 // getMDForRead returns an existing md for a read operation. Note that 2355 // mds will not be fetched here. 2356 func (fbo *folderBranchOps) getMDForRead( 2357 ctx context.Context, lState *kbfssync.LockState, rtype mdReadType) ( 2358 md ImmutableRootMetadata, err error) { 2359 if rtype != mdReadNeedIdentify && rtype != mdReadNoIdentify { 2360 panic("Invalid rtype in getMDLockedForRead") 2361 } 2362 2363 md = fbo.getTrustedHead(ctx, lState, mdCommit) 2364 if md != (ImmutableRootMetadata{}) { 2365 if rtype != mdReadNoIdentify { 2366 err = fbo.identifyOnce(ctx, md.ReadOnly()) 2367 } 2368 return md, err 2369 } 2370 2371 return ImmutableRootMetadata{}, MDWriteNeededInRequest{} 2372 } 2373 2374 // GetTLFHandle implements the KBFSOps interface for folderBranchOps. 2375 func (fbo *folderBranchOps) GetTLFHandle(ctx context.Context, _ Node) ( 2376 *tlfhandle.Handle, error) { 2377 lState := makeFBOLockState() 2378 md, _ := fbo.getHead(ctx, lState, mdNoCommit) 2379 if md == (ImmutableRootMetadata{}) { 2380 return nil, errors.New("No MD") 2381 } 2382 return md.GetTlfHandle(), nil 2383 } 2384 2385 // getMDForWriteOrRekeyLocked can fetch MDs, identify them and 2386 // contains the fancy logic. For reading use getMDLockedForRead. 2387 // Here we actually can fetch things from the server. 2388 // rekeys are untrusted. 2389 func (fbo *folderBranchOps) getMDForWriteOrRekeyLocked( 2390 ctx context.Context, lState *kbfssync.LockState, mdType mdUpdateType) ( 2391 md ImmutableRootMetadata, err error) { 2392 defer func() { 2393 if err != nil || mdType == mdRekey { 2394 return 2395 } 2396 err = fbo.identifyOnce(ctx, md.ReadOnly()) 2397 }() 2398 2399 md = fbo.getTrustedHead(ctx, lState, mdNoCommit) 2400 if md != (ImmutableRootMetadata{}) { 2401 return md, nil 2402 } 2403 2404 // MDs coming from from rekey notifications are marked untrusted. 2405 // 2406 // TODO: Make tests not take this code path. 2407 fbo.mdWriterLock.AssertLocked(lState) 2408 2409 // Not in cache, fetch from server and add to cache. First, see 2410 // if this device has any unmerged commits -- take the latest one. 2411 mdops := fbo.config.MDOps() 2412 2413 if fbo.config.Mode().UnmergedTLFsEnabled() { 2414 // get the head of the unmerged branch for this device (if any) 2415 md, err = mdops.GetUnmergedForTLF(ctx, fbo.id(), kbfsmd.NullBranchID) 2416 if err != nil { 2417 return ImmutableRootMetadata{}, err 2418 } 2419 } 2420 2421 mergedMD, err := mdops.GetForTLF(ctx, fbo.id(), nil) 2422 if err != nil { 2423 return ImmutableRootMetadata{}, err 2424 } 2425 2426 if mergedMD == (ImmutableRootMetadata{}) { 2427 return ImmutableRootMetadata{}, 2428 errors.WithStack(NoMergedMDError{fbo.id()}) 2429 } 2430 2431 if md == (ImmutableRootMetadata{}) { 2432 // There are no unmerged MDs for this device, so just use the current head. 2433 md = mergedMD 2434 } else { 2435 func() { 2436 fbo.headLock.Lock(lState) 2437 defer fbo.headLock.Unlock(lState) 2438 // We don't need to do this for merged head 2439 // because the setHeadLocked() already does 2440 // that anyway. 2441 fbo.setLatestMergedRevisionLocked(ctx, lState, mergedMD.Revision(), false) 2442 }() 2443 } 2444 2445 if md.data.Dir.Type != data.Dir && (!md.IsInitialized() || md.IsReadable()) { 2446 return ImmutableRootMetadata{}, errors.Errorf("Got undecryptable RMD for %s: initialized=%t, readable=%t", fbo.id(), md.IsInitialized(), md.IsReadable()) 2447 } 2448 2449 fbo.headLock.Lock(lState) 2450 defer fbo.headLock.Unlock(lState) 2451 headStatus := headTrusted 2452 if mdType == mdRekey { 2453 // If we already have a head (that has been filled after the initial 2454 // check, but before we acquired the lock), then just return it. 2455 if fbo.head != (ImmutableRootMetadata{}) { 2456 return fbo.head, nil 2457 } 2458 headStatus = headUntrusted 2459 } 2460 2461 err = fbo.setHeadLocked(ctx, lState, md, headStatus, mdToCommitType(md)) 2462 if err != nil { 2463 return ImmutableRootMetadata{}, err 2464 } 2465 2466 return md, nil 2467 } 2468 2469 func (fbo *folderBranchOps) getMDForReadHelper( 2470 ctx context.Context, lState *kbfssync.LockState, rtype mdReadType) ( 2471 ImmutableRootMetadata, error) { 2472 md, err := fbo.getMDForRead(ctx, lState, rtype) 2473 if err != nil { 2474 return ImmutableRootMetadata{}, err 2475 } 2476 if md.TlfID().Type() != tlf.Public { 2477 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 2478 if err != nil { 2479 return ImmutableRootMetadata{}, err 2480 } 2481 isReader, err := md.IsReader( 2482 ctx, fbo.config.KBPKI(), fbo.config, session.UID) 2483 if err != nil { 2484 return ImmutableRootMetadata{}, err 2485 } 2486 if !isReader { 2487 return ImmutableRootMetadata{}, tlfhandle.NewReadAccessError( 2488 md.GetTlfHandle(), session.Name, md.GetTlfHandle().GetCanonicalPath()) 2489 } 2490 } 2491 return md, nil 2492 } 2493 2494 // getMostRecentFullyMergedMD is a helper method that returns the most 2495 // recent merged MD that has been flushed to the server. This could 2496 // be different from the current local head if journaling is on. If 2497 // the journal is on a branch, it returns an error. 2498 func (fbo *folderBranchOps) getMostRecentFullyMergedMD(ctx context.Context) ( 2499 ImmutableRootMetadata, error) { 2500 mergedRev, err := fbo.getJournalPredecessorRevision(ctx) 2501 if err != nil { 2502 return ImmutableRootMetadata{}, err 2503 } 2504 2505 if mergedRev == kbfsmd.RevisionUninitialized { 2506 // No unflushed journal entries, so use the local head. 2507 lState := makeFBOLockState() 2508 return fbo.getMDForReadHelper(ctx, lState, mdReadNoIdentify) 2509 } 2510 2511 // Otherwise, use the specified revision. 2512 rmd, err := GetSingleMD(ctx, fbo.config, fbo.id(), kbfsmd.NullBranchID, 2513 mergedRev, kbfsmd.Merged, nil) 2514 if err != nil { 2515 return ImmutableRootMetadata{}, err 2516 } 2517 2518 fbo.vlog.CLogf( 2519 ctx, libkb.VLog1, "Most recent fully merged revision is %d", mergedRev) 2520 return rmd, nil 2521 } 2522 2523 func (fbo *folderBranchOps) getMDForReadNoIdentify( 2524 ctx context.Context, lState *kbfssync.LockState) ( 2525 ImmutableRootMetadata, error) { 2526 return fbo.getMDForReadHelper(ctx, lState, mdReadNoIdentify) 2527 } 2528 2529 func (fbo *folderBranchOps) getMDForReadNeedIdentify( 2530 ctx context.Context, lState *kbfssync.LockState) ( 2531 ImmutableRootMetadata, error) { 2532 return fbo.getMDForReadHelper(ctx, lState, mdReadNeedIdentify) 2533 } 2534 2535 // getMDForReadNeedIdentifyOnMaybeFirstAccess should be called by a 2536 // code path (like chat) that might be accessing this folder for the 2537 // first time. Other folderBranchOps methods like Lookup which know 2538 // the folder has already been accessed at least once (to get the root 2539 // node, for example) do not need to call this. Unlike other getMD 2540 // calls, this one may return a nil ImmutableRootMetadata along with a 2541 // nil error, to indicate that there isn't any MD for this TLF yet and 2542 // one must be created by the caller. 2543 func (fbo *folderBranchOps) getMDForReadNeedIdentifyOnMaybeFirstAccess( 2544 ctx context.Context, lState *kbfssync.LockState) ( 2545 ImmutableRootMetadata, error) { 2546 md, err := fbo.getMDForRead(ctx, lState, mdReadNeedIdentify) 2547 2548 if _, ok := err.(MDWriteNeededInRequest); ok { 2549 fbo.mdWriterLock.Lock(lState) 2550 defer fbo.mdWriterLock.Unlock(lState) 2551 md, err = fbo.getMDForWriteOrRekeyLocked(ctx, lState, mdWrite) 2552 } 2553 2554 if _, noMD := errors.Cause(err).(NoMergedMDError); noMD { 2555 return ImmutableRootMetadata{}, nil 2556 } 2557 2558 if err != nil { 2559 return ImmutableRootMetadata{}, err 2560 } 2561 2562 if md.TlfID().Type() != tlf.Public { 2563 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 2564 if err != nil { 2565 return ImmutableRootMetadata{}, err 2566 } 2567 isReader, err := md.IsReader( 2568 ctx, fbo.config.KBPKI(), fbo.config, session.UID) 2569 if err != nil { 2570 return ImmutableRootMetadata{}, err 2571 } 2572 if !isReader { 2573 return ImmutableRootMetadata{}, tlfhandle.NewReadAccessError( 2574 md.GetTlfHandle(), session.Name, md.GetTlfHandle().GetCanonicalPath()) 2575 } 2576 } 2577 2578 return md, nil 2579 } 2580 2581 func (fbo *folderBranchOps) getMDForWriteLockedForFilename( 2582 ctx context.Context, lState *kbfssync.LockState, filename string) ( 2583 ImmutableRootMetadata, error) { 2584 fbo.mdWriterLock.AssertLocked(lState) 2585 2586 md, err := fbo.getMDForWriteOrRekeyLocked(ctx, lState, mdWrite) 2587 if err != nil { 2588 return ImmutableRootMetadata{}, err 2589 } 2590 2591 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 2592 if err != nil { 2593 return ImmutableRootMetadata{}, err 2594 } 2595 isWriter, err := md.IsWriter( 2596 ctx, fbo.config.KBPKI(), fbo.config, session.UID, session.VerifyingKey) 2597 if err != nil { 2598 return ImmutableRootMetadata{}, err 2599 } 2600 if !isWriter { 2601 return ImmutableRootMetadata{}, tlfhandle.NewWriteAccessError( 2602 md.GetTlfHandle(), session.Name, filename) 2603 } 2604 2605 return md, nil 2606 } 2607 2608 func (fbo *folderBranchOps) getSuccessorMDForWriteLockedForFilename( 2609 ctx context.Context, lState *kbfssync.LockState, filename string) ( 2610 *RootMetadata, error) { 2611 fbo.mdWriterLock.AssertLocked(lState) 2612 2613 md, err := fbo.getMDForWriteLockedForFilename(ctx, lState, filename) 2614 if err != nil { 2615 return nil, err 2616 } 2617 2618 // Make a new successor of the current MD to hold the coming 2619 // writes. The caller must pass this into `finalizeMDWriteLocked` 2620 // or the changes will be lost. 2621 return md.MakeSuccessor(ctx, fbo.config.MetadataVersion(), 2622 fbo.config.Codec(), 2623 fbo.config.KeyManager(), fbo.config.KBPKI(), fbo.config.KBPKI(), 2624 fbo.config, md.mdID, true) 2625 } 2626 2627 // getSuccessorMDForWriteLocked returns a new RootMetadata object with 2628 // an incremented version number for modification. If the returned 2629 // object is put to the MDServer (via MDOps), mdWriterLock must be 2630 // held until then. (See comments for mdWriterLock above.) 2631 func (fbo *folderBranchOps) getSuccessorMDForWriteLocked( 2632 ctx context.Context, lState *kbfssync.LockState) (*RootMetadata, error) { 2633 return fbo.getSuccessorMDForWriteLockedForFilename(ctx, lState, "") 2634 } 2635 2636 // getMDForRekeyWriteLocked returns a nil `rmd` if it is a team TLF, 2637 // since that can't be rekeyed by KBFS. 2638 func (fbo *folderBranchOps) getMDForRekeyWriteLocked( 2639 ctx context.Context, lState *kbfssync.LockState) ( 2640 rmd *RootMetadata, lastWriterVerifyingKey kbfscrypto.VerifyingKey, 2641 wasRekeySet bool, err error) { 2642 fbo.mdWriterLock.AssertLocked(lState) 2643 2644 md, err := fbo.getMDForWriteOrRekeyLocked(ctx, lState, mdRekey) 2645 if err != nil { 2646 return nil, kbfscrypto.VerifyingKey{}, false, err 2647 } 2648 2649 if md.TypeForKeying() == tlf.TeamKeying { 2650 return nil, kbfscrypto.VerifyingKey{}, false, nil 2651 } 2652 2653 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 2654 if err != nil { 2655 return nil, kbfscrypto.VerifyingKey{}, false, err 2656 } 2657 2658 handle := md.GetTlfHandle() 2659 2660 // must be a reader or writer (it checks both.) 2661 if !handle.IsReader(session.UID) { 2662 return nil, kbfscrypto.VerifyingKey{}, false, 2663 NewRekeyPermissionError(md.GetTlfHandle(), session.Name) 2664 } 2665 2666 newMd, err := md.MakeSuccessor(ctx, fbo.config.MetadataVersion(), 2667 fbo.config.Codec(), 2668 fbo.config.KeyManager(), fbo.config.KBPKI(), fbo.config.KBPKI(), 2669 fbo.config, md.mdID, handle.IsWriter(session.UID)) 2670 if err != nil { 2671 return nil, kbfscrypto.VerifyingKey{}, false, err 2672 } 2673 2674 // readers shouldn't modify writer metadata 2675 if !handle.IsWriter(session.UID) && !newMd.IsWriterMetadataCopiedSet() { 2676 return nil, kbfscrypto.VerifyingKey{}, false, 2677 NewRekeyPermissionError(handle, session.Name) 2678 } 2679 2680 return newMd, md.LastModifyingWriterVerifyingKey(), md.IsRekeySet(), nil 2681 } 2682 2683 func (fbo *folderBranchOps) nowUnixNano() int64 { 2684 return fbo.config.Clock().Now().UnixNano() 2685 } 2686 2687 func (fbo *folderBranchOps) maybeUnembedAndPutBlocks(ctx context.Context, 2688 md *RootMetadata) (blockPutState, error) { 2689 if fbo.config.BlockSplitter().ShouldEmbedData( 2690 md.data.Changes.SizeEstimate()) { 2691 return nil, nil 2692 } 2693 2694 chargedTo, err := chargedToForTLF( 2695 ctx, fbo.config.KBPKI(), fbo.config.KBPKI(), fbo.config, 2696 md.GetTlfHandle()) 2697 if err != nil { 2698 return nil, err 2699 } 2700 2701 bps := newBlockPutStateMemory(1) 2702 err = fbo.prepper.unembedBlockChanges( 2703 ctx, bps, md, &md.data.Changes, chargedTo) 2704 if err != nil { 2705 return nil, err 2706 } 2707 2708 defer func() { 2709 if err != nil { 2710 fbo.fbm.cleanUpBlockState(md.ReadOnly(), bps, blockDeleteOnMDFail) 2711 } 2712 }() 2713 cacheType := DiskBlockAnyCache 2714 if fbo.isSyncedTlf() { 2715 cacheType = DiskBlockSyncCache 2716 } 2717 ptrsToDelete, err := doBlockPuts( 2718 ctx, fbo.config.BlockServer(), fbo.config.BlockCache(), 2719 fbo.config.Reporter(), fbo.log, fbo.deferLog, md.TlfID(), 2720 md.GetTlfHandle().GetCanonicalName(), bps, cacheType) 2721 if err != nil { 2722 return nil, err 2723 } 2724 if len(ptrsToDelete) > 0 { 2725 return nil, errors.Errorf("Unexpected pointers to delete after "+ 2726 "unembedding block changes in gc op: %v", ptrsToDelete) 2727 } 2728 return bps, nil 2729 } 2730 2731 // ResetRootBlock creates a new empty dir block and sets the given 2732 // metadata's root block to it. 2733 func ResetRootBlock(ctx context.Context, config Config, 2734 rmd *RootMetadata) (data.Block, data.BlockInfo, data.ReadyBlockData, error) { 2735 newDblock := data.NewDirBlock() 2736 chargedTo, err := chargedToForTLF( 2737 ctx, config.KBPKI(), config.KBPKI(), config, rmd.GetTlfHandle()) 2738 if err != nil { 2739 return nil, data.BlockInfo{}, data.ReadyBlockData{}, err 2740 } 2741 2742 info, plainSize, readyBlockData, err := 2743 data.ReadyBlock(ctx, config.BlockCache(), config.BlockOps(), 2744 rmd.ReadOnly(), newDblock, chargedTo, config.DefaultBlockType(), 2745 cacheHashBehavior(config, config, rmd.TlfID())) 2746 if err != nil { 2747 return nil, data.BlockInfo{}, data.ReadyBlockData{}, err 2748 } 2749 2750 now := config.Clock().Now().UnixNano() 2751 rmd.data.Dir = data.DirEntry{ 2752 BlockInfo: info, 2753 EntryInfo: data.EntryInfo{ 2754 Type: data.Dir, 2755 Size: uint64(plainSize), 2756 Mtime: now, 2757 Ctime: now, 2758 }, 2759 } 2760 prevDiskUsage := rmd.DiskUsage() 2761 rmd.SetDiskUsage(0) 2762 // Redundant, since this is called only for brand-new or 2763 // successor RMDs, but leave in to be defensive. 2764 rmd.ClearBlockChanges() 2765 co := newCreateOpForRootDir() 2766 rmd.AddOp(co) 2767 rmd.AddRefBlock(rmd.data.Dir.BlockInfo) 2768 // Set unref bytes to the previous disk usage, so that the 2769 // accounting works out. 2770 rmd.AddUnrefBytes(prevDiskUsage) 2771 return newDblock, info, readyBlockData, nil 2772 } 2773 2774 func (fbo *folderBranchOps) cacheHashBehavior() data.BlockCacheHashBehavior { 2775 return cacheHashBehavior(fbo.config, fbo.config, fbo.id()) 2776 } 2777 2778 func (fbo *folderBranchOps) initMDLocked( 2779 ctx context.Context, lState *kbfssync.LockState, md *RootMetadata) error { 2780 fbo.mdWriterLock.AssertLocked(lState) 2781 2782 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 2783 if err != nil { 2784 return err 2785 } 2786 2787 handle := md.GetTlfHandle() 2788 2789 // make sure we're a writer before rekeying or putting any blocks. 2790 isWriter, err := md.IsWriter( 2791 ctx, fbo.config.KBPKI(), fbo.config, session.UID, session.VerifyingKey) 2792 if err != nil { 2793 return err 2794 } 2795 if !isWriter { 2796 return tlfhandle.NewWriteAccessError( 2797 handle, session.Name, handle.GetCanonicalPath()) 2798 } 2799 2800 var expectedKeyGen kbfsmd.KeyGen 2801 var tlfCryptKey *kbfscrypto.TLFCryptKey 2802 switch md.TypeForKeying() { 2803 case tlf.PublicKeying: 2804 expectedKeyGen = kbfsmd.PublicKeyGen 2805 case tlf.PrivateKeying: 2806 var rekeyDone bool 2807 // create a new set of keys for this metadata 2808 rekeyDone, tlfCryptKey, err = fbo.config.KeyManager().Rekey(ctx, md, false) 2809 if err != nil { 2810 return err 2811 } 2812 if !rekeyDone { 2813 return errors.Errorf("Initial rekey unexpectedly not done for "+ 2814 "private TLF %v", md.TlfID()) 2815 } 2816 expectedKeyGen = kbfsmd.FirstValidKeyGen 2817 case tlf.TeamKeying: 2818 // Teams get their crypt key from the service, no need to 2819 // rekey in KBFS. 2820 tid, err := handle.FirstResolvedWriter().AsTeam() 2821 if err != nil { 2822 return err 2823 } 2824 keys, keyGen, err := fbo.config.KBPKI().GetTeamTLFCryptKeys( 2825 ctx, tid, kbfsmd.UnspecifiedKeyGen, fbo.oa()) 2826 if err != nil { 2827 return err 2828 } 2829 if keyGen < kbfsmd.FirstValidKeyGen { 2830 return errors.WithStack( 2831 kbfsmd.InvalidKeyGenerationError{TlfID: md.TlfID(), KeyGen: keyGen}) 2832 } 2833 expectedKeyGen = keyGen 2834 md.bareMd.SetLatestKeyGenerationForTeamTLF(keyGen) 2835 key, ok := keys[keyGen] 2836 if !ok { 2837 return errors.WithStack( 2838 kbfsmd.InvalidKeyGenerationError{TlfID: md.TlfID(), KeyGen: keyGen}) 2839 } 2840 tlfCryptKey = &key 2841 } 2842 keyGen := md.LatestKeyGeneration() 2843 if keyGen != expectedKeyGen { 2844 return kbfsmd.InvalidKeyGenerationError{TlfID: md.TlfID(), KeyGen: keyGen} 2845 } 2846 2847 // create a dblock since one doesn't exist yet 2848 newDblock, info, readyBlockData, err := ResetRootBlock(ctx, fbo.config, md) 2849 if err != nil { 2850 return err 2851 } 2852 2853 // Some other thread got here first, so give up and let it go 2854 // before we push anything to the servers. 2855 if h, _ := fbo.getHead( 2856 ctx, lState, mdNoCommit); h != (ImmutableRootMetadata{}) { 2857 fbo.vlog.CLogf(ctx, libkb.VLog1, "Head was already set, aborting") 2858 return nil 2859 } 2860 2861 cacheType := DiskBlockAnyCache 2862 if fbo.isSyncedTlf() { 2863 cacheType = DiskBlockSyncCache 2864 } 2865 if err = PutBlockCheckLimitErrs( 2866 ctx, fbo.config.BlockServer(), fbo.config.Reporter(), md.TlfID(), 2867 info.BlockPointer, readyBlockData, 2868 md.GetTlfHandle().GetCanonicalName(), cacheType); err != nil { 2869 return err 2870 } 2871 err = fbo.config.BlockCache().Put( 2872 info.BlockPointer, fbo.id(), newDblock, data.TransientEntry, 2873 fbo.cacheHashBehavior()) 2874 if err != nil { 2875 fbo.log.CDebugf( 2876 ctx, "Error caching new block %v: %+v", info.BlockPointer, err) 2877 } 2878 2879 bps, err := fbo.maybeUnembedAndPutBlocks(ctx, md) 2880 if err != nil { 2881 return err 2882 } 2883 2884 err = fbo.finalizeBlocks(ctx, bps) 2885 if err != nil { 2886 return err 2887 } 2888 2889 // Write out the new metadata. If journaling is enabled, we don't 2890 // want the rekey to hit the journal and possibly end up on a 2891 // conflict branch, so push straight to the server. 2892 mdOps := fbo.config.MDOps() 2893 if jManager, err := GetJournalManager(fbo.config); err == nil { 2894 mdOps = jManager.delegateMDOps 2895 } 2896 irmd, err := mdOps.Put( 2897 ctx, md, session.VerifyingKey, nil, keybase1.MDPriorityNormal, bps) 2898 isConflict := isRevisionConflict(err) 2899 if err != nil && !isConflict { 2900 return err 2901 } else if isConflict { 2902 return RekeyConflictError{err} 2903 } 2904 2905 fbo.headLock.Lock(lState) 2906 defer fbo.headLock.Unlock(lState) 2907 if fbo.head != (ImmutableRootMetadata{}) { 2908 return errors.Errorf( 2909 "%v: Unexpected MD ID during new MD initialization: %v", 2910 md.TlfID(), fbo.head.mdID) 2911 } 2912 2913 err = fbo.setNewInitialHeadLocked(ctx, lState, irmd) 2914 if err != nil { 2915 return err 2916 } 2917 2918 // cache any new TLF crypt key 2919 if tlfCryptKey != nil { 2920 err = fbo.config.KeyCache().PutTLFCryptKey( 2921 md.TlfID(), keyGen, *tlfCryptKey) 2922 if err != nil { 2923 return err 2924 } 2925 } 2926 2927 return nil 2928 } 2929 2930 func (fbo *folderBranchOps) checkNode(node Node) error { 2931 fb := node.GetFolderBranch() 2932 if fb != fbo.folderBranch { 2933 return WrongOpsError{fbo.folderBranch, fb} 2934 } 2935 return nil 2936 } 2937 2938 func (fbo *folderBranchOps) checkNodeForRead( 2939 ctx context.Context, node Node) error { 2940 err := fbo.checkNode(node) 2941 if err != nil { 2942 return err 2943 } 2944 2945 // If we're offline, only synced, non-archived data should be 2946 // available. TODO(KBFS-3585): add checks for unsynced TLFs. 2947 if !fbo.branch().IsArchived() { 2948 return nil 2949 } 2950 2951 services, _ := fbo.serviceStatus.CurrentStatus() 2952 if len(services) > 0 { 2953 fbo.vlog.CLogf( 2954 ctx, libkb.VLog1, "Failing read of archived data while offline; "+ 2955 "failing services=%v", services) 2956 h, err := fbo.GetTLFHandle(ctx, nil) 2957 if err != nil { 2958 return err 2959 } 2960 return OfflineArchivedError{h} 2961 } 2962 return nil 2963 } 2964 2965 func (fbo *folderBranchOps) checkNodeForWrite( 2966 ctx context.Context, node Node) error { 2967 err := fbo.checkNode(node) 2968 if err != nil { 2969 return err 2970 } 2971 if !node.Readonly(ctx) { 2972 return nil 2973 } 2974 2975 // This is a read-only node, so reject the write. 2976 p, err := fbo.pathFromNodeForRead(node) 2977 if err != nil { 2978 return err 2979 } 2980 return WriteToReadonlyNodeError{p.String()} 2981 } 2982 2983 // SetInitialHeadFromServer sets the head to the given 2984 // ImmutableRootMetadata, which must be retrieved from the MD server. 2985 func (fbo *folderBranchOps) SetInitialHeadFromServer( 2986 ctx context.Context, md ImmutableRootMetadata) (err error) { 2987 startTime, timer := fbo.startOp( 2988 ctx, "SetInitialHeadFromServer, revision=%d (%s)", 2989 md.Revision(), md.MergedStatus()) 2990 defer func() { 2991 fbo.endOp( 2992 ctx, startTime, timer, 2993 "SetInitialHeadFromServer, revision=%d (%s) done: %+v", 2994 md.Revision(), md.MergedStatus(), err) 2995 }() 2996 2997 var latestRootBlockFetch <-chan error 2998 partialSyncMD := md 2999 lState := makeFBOLockState() 3000 var setHead bool 3001 if md.IsReadable() && fbo.config.Mode().PrefetchWorkers() > 0 { 3002 // We `Get` the root block to ensure downstream prefetches 3003 // occur. Use a fresh context, in case `ctx` is canceled by 3004 // the caller before we complete. 3005 prefetchCtx := fbo.ctxWithFBOID(context.Background()) 3006 fbo.vlog.CLogf( 3007 ctx, libkb.VLog1, 3008 "Prefetching root block with a new context: FBOID=%s", 3009 prefetchCtx.Value(CtxFBOIDKey)) 3010 latestRootBlockFetch = fbo.kickOffRootBlockFetch(ctx, md) 3011 3012 // Kick off partial prefetching once the latest merged 3013 // revision is set. 3014 defer func() { 3015 if setHead && err == nil { 3016 fbo.kickOffPartialSyncIfNeeded(ctx, lState, partialSyncMD) 3017 } 3018 }() 3019 } else { 3020 fbo.log.CDebugf(ctx, 3021 "Setting an unreadable head with revision=%d", md.Revision()) 3022 } 3023 3024 // Return early if the head is already set. This avoids taking 3025 // mdWriterLock for no reason, and it also avoids any side effects 3026 // (e.g., calling `identifyOnce` and downloading the merged 3027 // head) if head is already set. 3028 head, headStatus := fbo.getHead(ctx, lState, mdNoCommit) 3029 if headStatus == headTrusted && head != (ImmutableRootMetadata{}) && head.mdID == md.mdID { 3030 fbo.vlog.CLogf( 3031 ctx, libkb.VLog1, "Head MD already set to revision %d (%s), no "+ 3032 "need to set initial head again", 3033 md.Revision(), md.MergedStatus()) 3034 return nil 3035 } 3036 3037 return runUnlessCanceled(ctx, func() error { 3038 if md.TlfID() != fbo.id() { 3039 return WrongOpsError{ 3040 fbo.folderBranch, data.FolderBranch{ 3041 Tlf: md.TlfID(), 3042 Branch: data.MasterBranch, 3043 }} 3044 } 3045 3046 // Always identify first when trying to initialize the folder, 3047 // even if we turn out not to be a writer. (We can't rely on 3048 // the identifyOnce call in getMDLocked, because that isn't 3049 // called from the initialization code path when the local 3050 // user is not a valid writer.) Also, we want to make sure we 3051 // fail before we set the head, otherwise future calls will 3052 // succeed incorrectly. 3053 err = fbo.identifyOnce(ctx, md.ReadOnly()) 3054 if err != nil { 3055 return err 3056 } 3057 3058 lState := makeFBOLockState() 3059 3060 fbo.mdWriterLock.Lock(lState) 3061 defer fbo.mdWriterLock.Unlock(lState) 3062 3063 if md.MergedStatus() == kbfsmd.Unmerged && fbo.bType != conflict { 3064 mdops := fbo.config.MDOps() 3065 mergedMD, err := mdops.GetForTLF(ctx, fbo.id(), nil) 3066 if err != nil { 3067 return err 3068 } 3069 partialSyncMD = mergedMD 3070 3071 func() { 3072 fbo.headLock.Lock(lState) 3073 defer fbo.headLock.Unlock(lState) 3074 fbo.setLatestMergedRevisionLocked(ctx, lState, 3075 mergedMD.Revision(), false) 3076 }() 3077 } 3078 3079 ct := mdToCommitType(md) 3080 if latestRootBlockFetch != nil { 3081 _, _, err := fbo.waitForRootBlockFetchAndSyncIfNeeded( 3082 ctx, md, latestRootBlockFetch, nil) 3083 if err != nil { 3084 fbo.log.CDebugf(ctx, 3085 "Couldn't fetch root block, so not commiting MD: %+v", err) 3086 ct = mdNoCommit 3087 } 3088 } 3089 3090 fbo.headLock.Lock(lState) 3091 defer fbo.headLock.Unlock(lState) 3092 3093 // Only update the head the first time; later it will be 3094 // updated either directly via writes or through the 3095 // background update processor. 3096 if fbo.head == (ImmutableRootMetadata{}) { 3097 setHead = true 3098 err = fbo.setInitialHeadTrustedLocked(ctx, lState, md, ct) 3099 if err != nil { 3100 return err 3101 } 3102 } else if headStatus == headUntrusted { 3103 err = fbo.validateHeadLocked(ctx, lState, md) 3104 if err != nil { 3105 return err 3106 } 3107 } 3108 return nil 3109 }) 3110 } 3111 3112 // SetInitialHeadToNew creates a brand-new ImmutableRootMetadata 3113 // object and sets the head to that. This is trusted. 3114 func (fbo *folderBranchOps) SetInitialHeadToNew( 3115 ctx context.Context, id tlf.ID, handle *tlfhandle.Handle) (err error) { 3116 startTime, timer := fbo.startOp(ctx, "SetInitialHeadToNew %s", id) 3117 defer func() { 3118 fbo.endOp( 3119 ctx, startTime, timer, "SetInitialHeadToNew %s done: %+v", id, err) 3120 }() 3121 3122 rmd, err := makeInitialRootMetadata( 3123 fbo.config.MetadataVersion(), id, handle) 3124 if err != nil { 3125 return err 3126 } 3127 3128 return runUnlessCanceled(ctx, func() error { 3129 // New heads can only be set for the MasterBranch. 3130 fb := data.FolderBranch{Tlf: rmd.TlfID(), Branch: data.MasterBranch} 3131 if fb != fbo.folderBranch { 3132 return WrongOpsError{fbo.folderBranch, fb} 3133 } 3134 3135 // Always identify first when trying to initialize the folder, 3136 // even if we turn out not to be a writer. (We can't rely on 3137 // the identifyOnce call in getMDLocked, because that isn't 3138 // called from the initialization code path when the local 3139 // user is not a valid writer.) Also, we want to make sure we 3140 // fail before we set the head, otherwise future calls will 3141 // succeed incorrectly. 3142 err = fbo.identifyOnce(ctx, rmd.ReadOnly()) 3143 if err != nil { 3144 return err 3145 } 3146 3147 lState := makeFBOLockState() 3148 3149 fbo.mdWriterLock.Lock(lState) 3150 defer fbo.mdWriterLock.Unlock(lState) 3151 return fbo.initMDLocked(ctx, lState, rmd) 3152 }) 3153 } 3154 3155 func getNodeIDStr(n Node) string { 3156 if n == nil { 3157 return "NodeID(nil)" 3158 } 3159 return fmt.Sprintf("NodeID(%v)", n.GetID()) 3160 } 3161 3162 func (fbo *folderBranchOps) getRootNode(ctx context.Context) ( 3163 node Node, ei data.EntryInfo, handle *tlfhandle.Handle, err error) { 3164 startTime, timer := fbo.startOp(ctx, "getRootNode") 3165 defer func() { 3166 fbo.endOp( 3167 ctx, startTime, timer, "getRootNode done: %s %+v", 3168 getNodeIDStr(node), err) 3169 }() 3170 3171 lState := makeFBOLockState() 3172 3173 var md ImmutableRootMetadata 3174 md, err = fbo.getMDForRead(ctx, lState, mdReadNoIdentify) 3175 if _, ok := err.(MDWriteNeededInRequest); ok { 3176 func() { 3177 fbo.mdWriterLock.Lock(lState) 3178 defer fbo.mdWriterLock.Unlock(lState) 3179 md, err = fbo.getMDForWriteOrRekeyLocked(ctx, lState, mdWrite) 3180 }() 3181 } 3182 if err != nil { 3183 return nil, data.EntryInfo{}, nil, err 3184 } 3185 3186 // we may be an unkeyed client 3187 err = isReadableOrError(ctx, fbo.config.KBPKI(), fbo.config, md.ReadOnly()) 3188 if err != nil { 3189 return nil, data.EntryInfo{}, nil, err 3190 } 3191 3192 handle = md.GetTlfHandle() 3193 node, err = fbo.nodeCache.GetOrCreate(md.data.Dir.BlockPointer, 3194 data.NewPathPartString(string(handle.GetCanonicalName()), nil), 3195 nil, data.Dir) 3196 if err != nil { 3197 return nil, data.EntryInfo{}, nil, err 3198 } 3199 3200 return node, md.Data().Dir.EntryInfo, handle, nil 3201 } 3202 3203 type makeNewBlock func() data.Block 3204 3205 // pathFromNodeHelper() shouldn't be called except by the helper 3206 // functions below. 3207 func (fbo *folderBranchOps) pathFromNodeHelper(n Node) (data.Path, error) { 3208 p := fbo.nodeCache.PathFromNode(n) 3209 if !p.IsValid() { 3210 return data.Path{}, errors.WithStack(InvalidPathError{p}) 3211 } 3212 return p, nil 3213 } 3214 3215 // Helper functions to clarify uses of pathFromNodeHelper() (see 3216 // nodeCache comments). 3217 3218 func (fbo *folderBranchOps) pathFromNodeForRead(n Node) (data.Path, error) { 3219 return fbo.pathFromNodeHelper(n) 3220 } 3221 3222 func (fbo *folderBranchOps) pathFromNodeForMDWriteLocked( 3223 lState *kbfssync.LockState, n Node) (data.Path, error) { 3224 fbo.mdWriterLock.AssertLocked(lState) 3225 return fbo.pathFromNodeHelper(n) 3226 } 3227 3228 func (fbo *folderBranchOps) getDirChildren(ctx context.Context, dir Node) ( 3229 children map[data.PathPartString]data.EntryInfo, err error) { 3230 fs := dir.GetFS(ctx) 3231 if fs != nil { 3232 fbo.vlog.CLogf(ctx, libkb.VLog1, "Getting children using an FS") 3233 fis, err := fs.ReadDir("") 3234 if err != nil { 3235 return nil, err 3236 } 3237 children = make(map[data.PathPartString]data.EntryInfo, len(fis)) 3238 for _, fi := range fis { 3239 name := fi.Name() 3240 ei := data.EntryInfoFromFileInfo(fi) 3241 if ei.Type == data.Sym { 3242 target, err := fs.Readlink(name) 3243 if err != nil { 3244 return nil, err 3245 } 3246 ei.SymPath = target 3247 } 3248 children[dir.ChildName(name)] = ei 3249 } 3250 return children, nil 3251 } 3252 3253 lState := makeFBOLockState() 3254 3255 dirPath, err := fbo.pathFromNodeForRead(dir) 3256 if err != nil { 3257 return nil, err 3258 } 3259 3260 if fbo.nodeCache.IsUnlinked(dir) { 3261 fbo.vlog.CLogf(ctx, libkb.VLog1, "Returning an empty children set for "+ 3262 "unlinked directory %v", dirPath.TailPointer()) 3263 return nil, nil 3264 } 3265 3266 md, err := fbo.getMDForReadNeedIdentify(ctx, lState) 3267 if err != nil { 3268 return nil, err 3269 } 3270 3271 return fbo.blocks.GetChildren(ctx, lState, md.ReadOnly(), dirPath) 3272 } 3273 3274 func (fbo *folderBranchOps) transformReadError( 3275 ctx context.Context, node Node, err error) error { 3276 _, isBlockNonExistent := 3277 errors.Cause(err).(kbfsblock.ServerErrorBlockNonExistent) 3278 if errors.Cause(err) != context.DeadlineExceeded && !isBlockNonExistent { 3279 return err 3280 } 3281 3282 if fbo.isSyncedTlf() { 3283 fbo.log.CWarningf(ctx, 3284 "Got unexpected read error on a synced TLF: %+v", err) 3285 return err 3286 } 3287 3288 if isBlockNonExistent { 3289 p := fbo.nodeCache.PathFromNode(node) 3290 if p.HasValidParent() { 3291 // Surface the block error for everything but the root 3292 // block, so we don't hide serious unexpected errors. 3293 return err 3294 } 3295 // Hopefully, this just means that we're using an out-of-date, 3296 // cached MD that's pointing us to GC'd blocks. 3297 fbo.log.CWarningf(ctx, 3298 "Transforming missing root block error for an unsynced TLF: %+v", 3299 err) 3300 } 3301 3302 // For unsynced TLFs, return a specific error to let the system 3303 // know to show a sync recommendation. 3304 h, hErr := fbo.GetTLFHandle(ctx, nil) 3305 if hErr != nil { 3306 fbo.log.CDebugf( 3307 ctx, "Couldn't get handle while transforming error: %+v", hErr) 3308 return err 3309 } 3310 return errors.WithStack(OfflineUnsyncedError{h}) 3311 } 3312 3313 func (fbo *folderBranchOps) GetDirChildren(ctx context.Context, dir Node) ( 3314 children map[data.PathPartString]data.EntryInfo, err error) { 3315 startTime, timer := fbo.startOp(ctx, "GetDirChildren %s", getNodeIDStr(dir)) 3316 defer func() { 3317 err = fbo.transformReadError(ctx, dir, err) 3318 fbo.endOp( 3319 ctx, startTime, timer, "GetDirChildren %s done, %d entries: %+v", 3320 getNodeIDStr(dir), len(children), err) 3321 }() 3322 3323 err = fbo.checkNodeForRead(ctx, dir) 3324 if err != nil { 3325 return nil, err 3326 } 3327 3328 var retChildren map[data.PathPartString]data.EntryInfo 3329 err = runUnlessCanceled(ctx, func() error { 3330 retChildren, err = fbo.getDirChildren(ctx, dir) 3331 return err 3332 }) 3333 if err != nil { 3334 return nil, err 3335 } 3336 3337 if dir.ShouldRetryOnDirRead(ctx) { 3338 err2 := fbo.SyncFromServer(ctx, fbo.folderBranch, nil) 3339 if err2 != nil { 3340 fbo.log.CDebugf(ctx, "Error syncing before retry: %+v", err2) 3341 return nil, nil 3342 } 3343 3344 fbo.vlog.CLogf( 3345 ctx, libkb.VLog1, "Retrying GetDirChildren of an empty directory") 3346 err = runUnlessCanceled(ctx, func() error { 3347 retChildren, err = fbo.getDirChildren(ctx, dir) 3348 return err 3349 }) 3350 if err != nil { 3351 return nil, err 3352 } 3353 } 3354 3355 return retChildren, nil 3356 } 3357 3358 func (fbo *folderBranchOps) makeFakeEntryID( 3359 ctx context.Context, dir Node, name data.PathPartString) ( 3360 id kbfsblock.ID, err error) { 3361 // Use the path of the node to generate the node ID, which will be 3362 // unique and deterministic within `fbo.nodeCache`. 3363 dirPath := fbo.nodeCache.PathFromNode(dir) 3364 return kbfsblock.MakePermanentID( 3365 []byte(dirPath.ChildPathNoPtr(name, fbo.makeObfuscator()).String()), 3366 fbo.config.BlockCryptVersion()) 3367 } 3368 3369 func (fbo *folderBranchOps) makeFakeDirEntry( 3370 ctx context.Context, dir Node, name data.PathPartString) ( 3371 de data.DirEntry, err error) { 3372 fbo.vlog.CLogf(ctx, libkb.VLog1, "Faking directory entry for %s", name) 3373 id, err := fbo.makeFakeEntryID(ctx, dir, name) 3374 if err != nil { 3375 return data.DirEntry{}, err 3376 } 3377 3378 now := fbo.nowUnixNano() 3379 de = data.DirEntry{ 3380 BlockInfo: data.BlockInfo{ 3381 BlockPointer: data.BlockPointer{ 3382 ID: id, 3383 DataVer: data.FirstValidVer, 3384 }, 3385 }, 3386 EntryInfo: data.EntryInfo{ 3387 Type: data.Dir, 3388 Size: 0, 3389 Mtime: now, 3390 Ctime: now, 3391 }, 3392 } 3393 return de, nil 3394 } 3395 3396 func (fbo *folderBranchOps) makeFakeFileEntry( 3397 ctx context.Context, dir Node, name data.PathPartString, fi os.FileInfo, 3398 sympath data.PathPartString) (de data.DirEntry, err error) { 3399 fbo.vlog.CLogf(ctx, libkb.VLog1, "Faking file entry for %s", name) 3400 id, err := fbo.makeFakeEntryID(ctx, dir, name) 3401 if err != nil { 3402 return data.DirEntry{}, err 3403 } 3404 3405 de = data.DirEntry{ 3406 BlockInfo: data.BlockInfo{ 3407 BlockPointer: data.BlockPointer{ 3408 ID: id, 3409 DataVer: data.FirstValidVer, 3410 }, 3411 }, 3412 EntryInfo: data.EntryInfoFromFileInfo(fi), 3413 } 3414 if de.Type == data.Sym { 3415 de.SymPath = sympath.Plaintext() 3416 } 3417 return de, nil 3418 } 3419 3420 func (fbo *folderBranchOps) processMissedLookup( 3421 ctx context.Context, lState *kbfssync.LockState, dir Node, 3422 name data.PathPartString, missErr error) ( 3423 node Node, ei data.EntryInfo, err error) { 3424 // Check if the directory node wants to autocreate this. 3425 autocreate, ctx, et, fi, sympath, ptr := dir.ShouldCreateMissedLookup( 3426 ctx, name) 3427 if !autocreate { 3428 return nil, data.EntryInfo{}, missErr 3429 } 3430 3431 switch et { 3432 case data.FakeDir: 3433 de, err := fbo.makeFakeDirEntry(ctx, dir, name) 3434 if err != nil { 3435 return nil, data.EntryInfo{}, missErr 3436 } 3437 node, err := fbo.blocks.GetChildNode(lState, dir, name, de) 3438 if err != nil { 3439 return nil, data.EntryInfo{}, err 3440 } 3441 return node, de.EntryInfo, nil 3442 case data.FakeFile: 3443 de, err := fbo.makeFakeFileEntry(ctx, dir, name, fi, sympath) 3444 if err != nil { 3445 return nil, data.EntryInfo{}, missErr 3446 } 3447 node, err := fbo.blocks.GetChildNode(lState, dir, name, de) 3448 if err != nil { 3449 return nil, data.EntryInfo{}, err 3450 } 3451 return node, de.EntryInfo, nil 3452 case data.RealDir: 3453 de := data.DirEntry{ 3454 BlockInfo: data.BlockInfo{ 3455 BlockPointer: ptr, 3456 }, 3457 EntryInfo: data.EntryInfo{ 3458 Type: data.Dir, 3459 Size: uint64(fi.Size()), 3460 Mtime: fi.ModTime().Unix(), 3461 Ctime: fi.ModTime().Unix(), 3462 }, 3463 } 3464 node, err := fbo.blocks.GetChildNode(lState, dir, name, de) 3465 if err != nil { 3466 return nil, data.EntryInfo{}, err 3467 } 3468 return node, de.EntryInfo, nil 3469 } 3470 3471 if (sympath.Plaintext() != "" && et != data.Sym) || 3472 (sympath.Plaintext() == "" && et == data.Sym) { 3473 return nil, data.EntryInfo{}, errors.Errorf( 3474 "Invalid sympath %s for entry type %s", sympath, et) 3475 } 3476 3477 fbo.vlog.CLogf( 3478 ctx, libkb.VLog1, 3479 "Auto-creating %s of type %s after a missed lookup", name, et) 3480 switch et { 3481 case data.File: 3482 return fbo.CreateFile(ctx, dir, name, false, NoExcl) 3483 case data.Exec: 3484 return fbo.CreateFile(ctx, dir, name, true, NoExcl) 3485 case data.Dir: 3486 return fbo.CreateDir(ctx, dir, name) 3487 case data.Sym: 3488 ei, err := fbo.CreateLink(ctx, dir, name, sympath) 3489 return nil, ei, err 3490 default: 3491 return nil, data.EntryInfo{}, errors.Errorf("Unknown entry type %s", et) 3492 } 3493 } 3494 3495 func (fbo *folderBranchOps) statUsingFS( 3496 ctx context.Context, lState *kbfssync.LockState, node Node, 3497 name data.PathPartString) (de data.DirEntry, ok bool, err error) { 3498 if node == nil { 3499 return data.DirEntry{}, false, nil 3500 } 3501 3502 // First check if this is needs to be a faked-out node. 3503 autocreate, _, et, fi, sympath, ptr := node.ShouldCreateMissedLookup( 3504 ctx, name) 3505 if autocreate { 3506 switch et { 3507 case data.FakeDir: 3508 de, err := fbo.makeFakeDirEntry(ctx, node, name) 3509 if err != nil { 3510 return data.DirEntry{}, false, err 3511 } 3512 return de, true, nil 3513 case data.FakeFile: 3514 de, err = fbo.makeFakeFileEntry(ctx, node, name, fi, sympath) 3515 if err != nil { 3516 return data.DirEntry{}, false, err 3517 } 3518 return de, true, nil 3519 case data.RealDir: 3520 de := data.DirEntry{ 3521 BlockInfo: data.BlockInfo{ 3522 BlockPointer: ptr, 3523 }, 3524 EntryInfo: data.EntryInfo{ 3525 Type: data.Dir, 3526 Size: uint64(fi.Size()), 3527 Mtime: fi.ModTime().Unix(), 3528 Ctime: fi.ModTime().Unix(), 3529 }, 3530 } 3531 return de, true, nil 3532 } 3533 } 3534 3535 fs := node.GetFS(ctx) 3536 if fs == nil { 3537 return data.DirEntry{}, false, nil 3538 } 3539 3540 fbo.vlog.CLogf(ctx, libkb.VLog1, "Using an FS to satisfy stat of %s", name) 3541 3542 fi, err = fs.Lstat(name.Plaintext()) 3543 if err != nil { 3544 return data.DirEntry{}, false, err 3545 } 3546 3547 if fi.Mode()&os.ModeSymlink != 0 { 3548 sympathPlain, err := fs.Readlink(name.Plaintext()) 3549 if err != nil { 3550 return data.DirEntry{}, false, err 3551 } 3552 sympath = node.ChildName(sympathPlain) 3553 } 3554 3555 de, err = fbo.makeFakeFileEntry(ctx, node, name, fi, sympath) 3556 if err != nil { 3557 return data.DirEntry{}, false, err 3558 } 3559 return de, true, nil 3560 } 3561 3562 func (fbo *folderBranchOps) lookup( 3563 ctx context.Context, dir Node, name data.PathPartString) ( 3564 node Node, de data.DirEntry, err error) { 3565 lState := makeFBOLockState() 3566 3567 de, ok, err := fbo.statUsingFS(ctx, lState, dir, name) 3568 if err != nil { 3569 return nil, data.DirEntry{}, err 3570 } 3571 if ok { 3572 node, err := fbo.blocks.GetChildNode(lState, dir, name, de) 3573 if err != nil { 3574 return nil, data.DirEntry{}, err 3575 } 3576 return node, de, nil 3577 } 3578 3579 if fbo.nodeCache.IsUnlinked(dir) { 3580 fbo.vlog.CLogf( 3581 ctx, libkb.VLog1, "Refusing a lookup for unlinked directory %v", 3582 fbo.nodeCache.PathFromNode(dir).TailPointer()) 3583 return nil, data.DirEntry{}, idutil.NoSuchNameError{Name: name.String()} 3584 } 3585 3586 md, err := fbo.getMDForReadNeedIdentify(ctx, lState) 3587 if err != nil { 3588 return nil, data.DirEntry{}, err 3589 } 3590 3591 node, de, err = fbo.blocks.Lookup(ctx, lState, md.ReadOnly(), dir, name) 3592 if _, isMiss := errors.Cause(err).(idutil.NoSuchNameError); isMiss { 3593 node, de.EntryInfo, err = fbo.processMissedLookup( 3594 ctx, lState, dir, name, err) 3595 if _, exists := errors.Cause(err).(data.NameExistsError); exists { 3596 // Someone raced us to create the entry, so return the 3597 // new entry. 3598 node, de, err = fbo.blocks.Lookup( 3599 ctx, lState, md.ReadOnly(), dir, name) 3600 } 3601 } 3602 return node, de, err 3603 } 3604 3605 func (fbo *folderBranchOps) Lookup( 3606 ctx context.Context, dir Node, name data.PathPartString) ( 3607 node Node, ei data.EntryInfo, err error) { 3608 startTime, timer := fbo.startOp( 3609 ctx, "Lookup %s %s", getNodeIDStr(dir), name) 3610 defer func() { 3611 err = fbo.transformReadError(ctx, dir, err) 3612 fbo.endOp( 3613 ctx, startTime, timer, "Lookup %s %s done: %v %+v", 3614 getNodeIDStr(dir), name, getNodeIDStr(node), err) 3615 }() 3616 3617 err = fbo.checkNodeForRead(ctx, dir) 3618 if err != nil { 3619 return nil, data.EntryInfo{}, err 3620 } 3621 3622 // It's racy for the goroutine to write directly to return param 3623 // `node`, so use a new param for that. 3624 var n Node 3625 var de data.DirEntry 3626 err = runUnlessCanceled(ctx, func() error { 3627 var err error 3628 n, de, err = fbo.lookup(ctx, dir, name) 3629 return err 3630 }) 3631 // Only retry the lookup potentially if the lookup missed. 3632 if err != nil { 3633 if _, isMiss := errors.Cause(err).(idutil.NoSuchNameError); !isMiss { 3634 return nil, data.EntryInfo{}, err 3635 } 3636 } 3637 3638 if dir.ShouldRetryOnDirRead(ctx) { 3639 err2 := fbo.SyncFromServer(ctx, fbo.folderBranch, nil) 3640 if err2 != nil { 3641 fbo.log.CDebugf(ctx, "Error syncing before retry: %+v", err2) 3642 return n, de.EntryInfo, err 3643 } 3644 3645 fbo.vlog.CLogf( 3646 ctx, libkb.VLog1, "Retrying lookup of an empty directory") 3647 err = runUnlessCanceled(ctx, func() error { 3648 var err error 3649 n, de, err = fbo.lookup(ctx, dir, name) 3650 return err 3651 }) 3652 } 3653 if err != nil { 3654 return nil, data.EntryInfo{}, err 3655 } 3656 return n, de.EntryInfo, nil 3657 } 3658 3659 // statEntry is like Stat, but it returns a DirEntry. This is used by 3660 // tests. 3661 func (fbo *folderBranchOps) statEntry(ctx context.Context, node Node) ( 3662 de data.DirEntry, err error) { 3663 defer func() { 3664 err = fbo.transformReadError(ctx, node, err) 3665 }() 3666 err = fbo.checkNodeForRead(ctx, node) 3667 if err != nil { 3668 return data.DirEntry{}, err 3669 } 3670 3671 nodePath, err := fbo.pathFromNodeForRead(node) 3672 if err != nil { 3673 return data.DirEntry{}, err 3674 } 3675 3676 lState := makeFBOLockState() 3677 var md ImmutableRootMetadata 3678 if nodePath.HasValidParent() { 3679 // Look up the node for the parent, and see if it has an FS 3680 // that can be used to stat `node`. 3681 parentPath := nodePath.ParentPath() 3682 parentNode := fbo.nodeCache.Get(parentPath.TailPointer().Ref()) 3683 de, ok, err := fbo.statUsingFS( 3684 ctx, lState, parentNode, node.GetBasename()) 3685 if err != nil { 3686 return data.DirEntry{}, err 3687 } 3688 if ok { 3689 return de, nil 3690 } 3691 3692 // Otherwise, proceed with the usual way. 3693 md, err = fbo.getMDForReadNeedIdentify(ctx, lState) 3694 // And handle the error, err is local to this block 3695 // shadowing the err in the surrounding block. 3696 if err != nil { 3697 return data.DirEntry{}, err 3698 } 3699 } else { 3700 // If nodePath has no valid parent, it's just the TLF root, so 3701 // we don't need an identify in this case. Note: we don't 3702 // support FS-based stats for the root directory. 3703 md, err = fbo.getMDForReadNoIdentify(ctx, lState) 3704 } 3705 if err != nil { 3706 return data.DirEntry{}, err 3707 } 3708 3709 return fbo.blocks.GetEntryEvenIfDeleted( 3710 ctx, lState, md.ReadOnly(), nodePath) 3711 } 3712 3713 func (fbo *folderBranchOps) deferLogIfErr( 3714 ctx context.Context, err error, fs string, args ...interface{}) { 3715 if err != nil { 3716 fbo.defer2Log.CDebugf(ctx, fs, args...) 3717 } else { 3718 fbo.defer2Vlog.CLogf(ctx, libkb.VLog1, fs, args...) 3719 } 3720 } 3721 3722 func (fbo *folderBranchOps) Stat(ctx context.Context, node Node) ( 3723 ei data.EntryInfo, err error) { 3724 // Stats are common and clog up the logs, so only print to vlog. 3725 fbo.vlog.CLogf(ctx, libkb.VLog1, "Stat %s", getNodeIDStr(node)) 3726 defer func() { 3727 fbo.deferLogIfErr(ctx, err, "Stat %s (%d bytes) done: %+v", 3728 getNodeIDStr(node), ei.Size, err) 3729 }() 3730 3731 var de data.DirEntry 3732 err = runUnlessCanceled(ctx, func() error { 3733 de, err = fbo.statEntry(ctx, node) 3734 return err 3735 }) 3736 if err != nil { 3737 return data.EntryInfo{}, err 3738 } 3739 return de.EntryInfo, nil 3740 } 3741 3742 func (fbo *folderBranchOps) GetNodeMetadata(ctx context.Context, node Node) ( 3743 res NodeMetadata, err error) { 3744 startTime, timer := fbo.startOp( 3745 ctx, "GetNodeMetadata %s", getNodeIDStr(node)) 3746 defer func() { 3747 fbo.endOp( 3748 ctx, startTime, timer, "GetNodeMetadata %s done: %+v", 3749 getNodeIDStr(node), err) 3750 }() 3751 3752 var de data.DirEntry 3753 err = runUnlessCanceled(ctx, func() error { 3754 de, err = fbo.statEntry(ctx, node) 3755 return err 3756 }) 3757 if err != nil { 3758 return res, err 3759 } 3760 res.BlockInfo = de.BlockInfo 3761 3762 id := de.TeamWriter.AsUserOrTeam() 3763 if id.IsNil() { 3764 id = de.Writer 3765 } 3766 if id.IsNil() { 3767 id = de.Creator 3768 } 3769 // Only set the last resolved writer if it's really a user ID. 3770 // This works around an old teams bug where the TeamWriter isn't 3771 // set. See KBFS-2939. 3772 if id.IsUser() { 3773 res.LastWriterUnverified, err = 3774 fbo.config.KBPKI().GetNormalizedUsername(ctx, id, fbo.oa()) 3775 if err != nil { 3776 return res, err 3777 } 3778 } 3779 res.PrefetchStatus = fbo.config.PrefetchStatus(ctx, fbo.id(), 3780 res.BlockInfo.BlockPointer) 3781 if res.PrefetchStatus == TriggeredPrefetch { 3782 byteStatus, err := fbo.config.BlockOps().Prefetcher().Status( 3783 ctx, res.BlockInfo.BlockPointer) 3784 if err != nil { 3785 return res, err 3786 } 3787 res.PrefetchProgress = &byteStatus 3788 } 3789 return res, nil 3790 } 3791 3792 // Returns whether the given error is one that shouldn't block the 3793 // removal of a file or directory. 3794 // 3795 // TODO: Consider other errors recoverable, e.g. ones that arise from 3796 // present but corrupted blocks? 3797 func isRecoverableBlockErrorForRemoval(err error) bool { 3798 return isRecoverableBlockError(err) 3799 } 3800 3801 func isRetriableError(err error, retries int) bool { 3802 _, isExclOnUnmergedError := err.(ExclOnUnmergedError) 3803 _, isUnmergedSelfConflictError := err.(UnmergedSelfConflictError) 3804 recoverable := isExclOnUnmergedError || isUnmergedSelfConflictError || 3805 isRecoverableBlockError(err) 3806 return recoverable && retries < maxRetriesOnRecoverableErrors 3807 } 3808 3809 func (fbo *folderBranchOps) finalizeBlocks( 3810 ctx context.Context, bps blockPutState) error { 3811 if bps == nil { 3812 return nil 3813 } 3814 bcache := fbo.config.BlockCache() 3815 for _, newPtr := range bps.Ptrs() { 3816 // only cache this block if we made a brand new block, not if 3817 // we just incref'd some other block. 3818 if !newPtr.IsFirstRef() { 3819 continue 3820 } 3821 block, err := bps.GetBlock(ctx, newPtr) 3822 if err != nil { 3823 fbo.log.CDebugf(ctx, "Error getting block for %v: %+v", newPtr, err) 3824 } 3825 if err := bcache.Put( 3826 newPtr, fbo.id(), block, data.TransientEntry, 3827 fbo.cacheHashBehavior()); err != nil { 3828 fbo.log.CDebugf( 3829 ctx, "Error caching new block %v: %+v", newPtr, err) 3830 } 3831 } 3832 return nil 3833 } 3834 3835 // Returns true if the passed error indicates a revision conflict. 3836 func isRevisionConflict(err error) bool { 3837 if err == nil { 3838 return false 3839 } 3840 _, isConflictRevision := err.(kbfsmd.ServerErrorConflictRevision) 3841 _, isConflictPrevRoot := err.(kbfsmd.ServerErrorConflictPrevRoot) 3842 _, isConflictDiskUsage := err.(kbfsmd.ServerErrorConflictDiskUsage) 3843 _, isConditionFailed := err.(kbfsmd.ServerErrorConditionFailed) 3844 _, isConflictFolderMapping := err.(kbfsmd.ServerErrorConflictFolderMapping) 3845 _, isJournal := err.(MDJournalConflictError) 3846 return isConflictRevision || isConflictPrevRoot || 3847 isConflictDiskUsage || isConditionFailed || 3848 isConflictFolderMapping || isJournal 3849 } 3850 3851 func (fbo *folderBranchOps) getConvID( 3852 ctx context.Context, handle *tlfhandle.Handle) ( 3853 chat1.ConversationID, error) { 3854 fbo.convLock.Lock() 3855 defer fbo.convLock.Unlock() 3856 if len(fbo.convID) == 0 { 3857 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 3858 if err != nil { 3859 return nil, err 3860 } 3861 channelName := string(session.Name) 3862 3863 id, err := fbo.config.Chat().GetConversationID( 3864 ctx, handle.GetCanonicalName(), fbo.id().Type(), 3865 channelName, chat1.TopicType_KBFSFILEEDIT) 3866 if err != nil { 3867 return nil, err 3868 } 3869 fbo.log.CDebugf(ctx, "Conversation ID is %s for this writer (%s)", 3870 id, channelName) 3871 fbo.convID = id 3872 } 3873 return fbo.convID, nil 3874 } 3875 3876 func (fbo *folderBranchOps) sendEditNotifications( 3877 ctx context.Context, rmd ImmutableRootMetadata, body string) error { 3878 handle := rmd.GetTlfHandle() 3879 convID, err := fbo.getConvID(ctx, handle) 3880 if err != nil { 3881 return err 3882 } 3883 return fbo.config.Chat().SendTextMessage( 3884 ctx, handle.GetCanonicalName(), fbo.id().Type(), convID, body) 3885 } 3886 3887 func (fbo *folderBranchOps) makeEditNotifications( 3888 ctx context.Context, rmd ImmutableRootMetadata) ( 3889 edits []kbfsedits.NotificationMessage, err error) { 3890 if rmd.IsWriterMetadataCopiedSet() { 3891 return nil, nil 3892 } 3893 3894 if rmd.MergedStatus() != kbfsmd.Merged { 3895 return nil, nil 3896 } 3897 3898 // If this MD is coming from the journal or from the conflict 3899 // resolver, the final paths will not be set on the ops. Use 3900 // crChains to set them. 3901 ops := pathSortedOps(rmd.data.Changes.Ops) 3902 if fbo.config.Mode().IsTestMode() { 3903 // Clear out the final paths to simulate in tests what happens 3904 // when MDs are read fresh from the journal. 3905 opsCopy := make(pathSortedOps, len(ops)) 3906 for i, op := range ops { 3907 opsCopy[i] = op.deepCopy() 3908 opsCopy[i].setFinalPath(data.Path{}) 3909 } 3910 ops = opsCopy 3911 } 3912 3913 isResolution := false 3914 if len(ops) > 0 { 3915 _, isResolution = ops[0].(*resolutionOp) 3916 } 3917 if isResolution || TLFJournalEnabled(fbo.config, fbo.id()) { 3918 chains, err := newCRChainsForIRMDs( 3919 ctx, fbo.config.Codec(), fbo.config, []ImmutableRootMetadata{rmd}, 3920 &fbo.blocks, true) 3921 if err != nil { 3922 return nil, err 3923 } 3924 err = fbo.blocks.populateChainPaths(ctx, fbo.log, chains, true) 3925 if err != nil { 3926 return nil, err 3927 } 3928 3929 // The crChains creation process splits up a rename op into 3930 // a delete and a create. Turn them back into a rename. 3931 err = chains.revertRenames(ops) 3932 if err != nil { 3933 return nil, err 3934 } 3935 3936 ops = pathSortedOps(make([]op, 0, len(ops))) 3937 for _, chain := range chains.byMostRecent { 3938 ops = append(ops, chain.ops...) 3939 } 3940 // Make sure the ops are in increasing order by path length, 3941 // so e.g. file creates come before file modifies. 3942 sort.Stable(ops) 3943 3944 for _, op := range ops { 3945 // Temporary debugging for the case where an op has an 3946 // invalid path (HOTPOT-803). Just printing something 3947 // here (before falling through to the more extension 3948 // debug statement below) to make it clear that we did go 3949 // through chain population. 3950 if !op.getFinalPath().IsValid() { 3951 fbo.log.CDebugf( 3952 ctx, "HOTPOT-803: Op %s missing path after populating "+ 3953 "chain paths", op) 3954 break 3955 } 3956 } 3957 } 3958 3959 rev := rmd.Revision() 3960 // We want the server's view of the time. 3961 revTime := rmd.localTimestamp 3962 if offset, ok := fbo.config.MDServer().OffsetFromServerTime(); ok { 3963 revTime = revTime.Add(-offset) 3964 } 3965 3966 for _, op := range ops { 3967 // Temporary debugging for the case where an op has an invalid 3968 // path (HOTPOT-803). 3969 if !op.getFinalPath().IsValid() { 3970 fbo.log.CDebugf( 3971 ctx, "HOTPOT-803: Op %s has no valid path; "+ 3972 "rev=%d, all ops=%v", op, rmd.Revision(), ops) 3973 if fbo.config.Mode().IsTestMode() { 3974 panic("Op missing path") 3975 } 3976 } 3977 3978 edit := op.ToEditNotification( 3979 rev, revTime, rmd.lastWriterVerifyingKey, 3980 rmd.LastModifyingWriter(), fbo.id()) 3981 if edit != nil { 3982 edits = append(edits, *edit) 3983 } 3984 } 3985 return edits, nil 3986 } 3987 3988 func (fbo *folderBranchOps) handleEditNotifications( 3989 ctx context.Context, rmd ImmutableRootMetadata) error { 3990 if !fbo.config.Mode().SendEditNotificationsEnabled() { 3991 return nil 3992 } 3993 3994 edits, err := fbo.makeEditNotifications(ctx, rmd) 3995 if err != nil { 3996 return err 3997 } 3998 if len(edits) == 0 { 3999 return nil 4000 } 4001 4002 body, err := kbfsedits.Prepare(edits) 4003 if err != nil { 4004 return err 4005 } 4006 return fbo.sendEditNotifications(ctx, rmd, body) 4007 } 4008 4009 func (fbo *folderBranchOps) handleUnflushedEditNotifications( 4010 ctx context.Context, rmd ImmutableRootMetadata) error { 4011 if !fbo.config.Mode().SendEditNotificationsEnabled() { 4012 return nil 4013 } 4014 4015 edits, err := fbo.makeEditNotifications(ctx, rmd) 4016 if err != nil { 4017 return err 4018 } 4019 session, err := idutil.GetCurrentSessionIfPossible( 4020 ctx, fbo.config.KBPKI(), true) 4021 if err != nil { 4022 return err 4023 } 4024 fbo.editHistory.AddUnflushedNotifications(string(session.Name), edits) 4025 4026 tlfName := rmd.GetTlfHandle().GetCanonicalName() 4027 fbo.config.UserHistory().UpdateHistory( 4028 tlfName, fbo.id().Type(), fbo.editHistory, string(session.Name)) 4029 return nil 4030 } 4031 4032 func (fbo *folderBranchOps) finalizeMDWriteLocked(ctx context.Context, 4033 lState *kbfssync.LockState, md *RootMetadata, bps blockPutState, excl Excl, 4034 notifyFn func(ImmutableRootMetadata) error) ( 4035 err error) { 4036 fbo.mdWriterLock.AssertLocked(lState) 4037 4038 // finally, write out the new metadata 4039 mdops := fbo.config.MDOps() 4040 4041 doUnmergedPut := true 4042 mergedRev := kbfsmd.RevisionUninitialized 4043 4044 oldPrevRoot := md.PrevRoot() 4045 4046 var irmd ImmutableRootMetadata 4047 4048 // This puts on a delay on any cancellations arriving to ctx. It is intended 4049 // to work sort of like a critical section, except that there isn't an 4050 // explicit call to exit the critical section. The cancellation, if any, is 4051 // triggered after a timeout (i.e. 4052 // fbo.config.DelayedCancellationGracePeriod()). 4053 // 4054 // The purpose of trying to avoid cancellation once we start MD write is to 4055 // avoid having an unpredictable perceived MD state. That is, when 4056 // runUnlessCanceled returns Canceled on cancellation, application receives 4057 // an EINTR, and would assume the operation didn't succeed. But the MD write 4058 // continues, and there's a chance the write will succeed, meaning the 4059 // operation succeeds. This contradicts with the application's perception 4060 // through error code and can lead to horrible situations. An easily caught 4061 // situation is when application calls Create with O_EXCL set, gets an EINTR 4062 // while MD write succeeds, retries and gets an EEXIST error. If users hit 4063 // Ctrl-C, this might not be a big deal. However, it also happens for other 4064 // interrupts. For applications that use signals to communicate, e.g. 4065 // SIGALRM and SIGUSR1, this can happen pretty often, which renders broken. 4066 if err = libcontext.EnableDelayedCancellationWithGracePeriod( 4067 ctx, fbo.config.DelayedCancellationGracePeriod()); err != nil { 4068 return err 4069 } 4070 // we don't explicitly clean up (by using a defer) CancellationDelayer here 4071 // because sometimes fuse makes another call using the same ctx. For example, in 4072 // fuse's Create call handler, a dir.Create is followed by an Attr call. If 4073 // we do a deferred cleanup here, if an interrupt has been received, it can 4074 // cause ctx to be canceled before Attr call finishes, which causes FUSE to 4075 // return EINTR for the Create request. But at this point, the request may 4076 // have already succeeded. Returning EINTR makes application thinks the file 4077 // is not created successfully. 4078 4079 err = fbo.finalizeBlocks(ctx, bps) 4080 if err != nil { 4081 return err 4082 } 4083 4084 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 4085 if err != nil { 4086 return err 4087 } 4088 4089 isMerged := !fbo.isUnmergedLocked(lState) 4090 if isMerged { 4091 // only do a normal Put if we're not already staged. 4092 irmd, err = mdops.Put( 4093 ctx, md, session.VerifyingKey, nil, keybase1.MDPriorityNormal, bps) 4094 if doUnmergedPut = isRevisionConflict(err); doUnmergedPut { 4095 fbo.log.CDebugf(ctx, "Conflict: %v", err) 4096 mergedRev = md.Revision() 4097 4098 if excl == WithExcl { 4099 // If this was caused by an exclusive create, we shouldn't do an 4100 // kbfsmd.UnmergedPut, but rather try to get newest update from server, and 4101 // retry afterwards. 4102 err = fbo.getAndApplyMDUpdates(ctx, 4103 lState, nil, fbo.applyMDUpdatesLocked) 4104 if err != nil { 4105 return err 4106 } 4107 return ExclOnUnmergedError{} 4108 } 4109 } else if err != nil { 4110 return err 4111 } 4112 } else if excl == WithExcl { 4113 return ExclOnUnmergedError{} 4114 } 4115 4116 doResolve := false 4117 resolveMergedRev := mergedRev 4118 if doUnmergedPut { 4119 // We're out of date, and this is not an exclusive write, so put it as an 4120 // unmerged MD. 4121 irmd, err = mdops.PutUnmerged(ctx, md, session.VerifyingKey, bps) 4122 if isRevisionConflict(err) { 4123 // Self-conflicts are retried in `doMDWriteWithRetry`. 4124 return UnmergedSelfConflictError{err} 4125 } else if err != nil { 4126 // If a PutUnmerged fails, we are in a bad situation: if 4127 // we fail, but the put succeeded, then dirty data will 4128 // remain cached locally and will be re-tried 4129 // (non-idempotently) on the next sync call. This should 4130 // be a very rare situation when journaling is enabled, so 4131 // instead let's pretend it succeeded so that the cached 4132 // data is cleared and the nodeCache is updated. If we're 4133 // wrong, and the update didn't make it to the server, 4134 // then the next call will get an 4135 // kbfsmd.UnmergedSelfConflictError but fail to find any new 4136 // updates and fail the operation, but things will get 4137 // fixed up once conflict resolution finally completes. 4138 // 4139 // TODO: how confused will the kernel cache get if the 4140 // pointers are updated but the file system operation 4141 // still gets an error returned by the wrapper function 4142 // that calls us (in the event of a user cancellation)? 4143 fbo.log.CInfof(ctx, "Ignoring a PutUnmerged error: %+v", err) 4144 err = encryptMDPrivateData( 4145 ctx, fbo.config.Codec(), fbo.config.Crypto(), 4146 fbo.config.Crypto(), fbo.config.KeyManager(), session.UID, md) 4147 if err != nil { 4148 return err 4149 } 4150 mdID, err := kbfsmd.MakeID(fbo.config.Codec(), md.bareMd) 4151 if err != nil { 4152 return err 4153 } 4154 irmd = MakeImmutableRootMetadata( 4155 md, session.VerifyingKey, mdID, fbo.config.Clock().Now(), true) 4156 err = fbo.config.MDCache().Put(irmd) 4157 if err != nil { 4158 return err 4159 } 4160 } 4161 unmergedBID := md.BID() 4162 fbo.setBranchIDLocked(lState, unmergedBID) 4163 doResolve = true 4164 } else { 4165 fbo.setBranchIDLocked(lState, kbfsmd.NullBranchID) 4166 4167 if md.IsRekeySet() && !md.IsWriterMetadataCopiedSet() { 4168 // Queue this folder for rekey if the bit was set and it's not a copy. 4169 // This is for the case where we're coming out of conflict resolution. 4170 // So why don't we do this in finalizeResolution? Well, we do but we don't 4171 // want to block on a rekey so we queue it. Because of that it may fail 4172 // due to a conflict with some subsequent write. By also handling it here 4173 // we'll always retry if we notice we haven't been successful in clearing 4174 // the bit yet. Note that I haven't actually seen this happen but it seems 4175 // theoretically possible. 4176 defer fbo.config.RekeyQueue().Enqueue(md.TlfID()) 4177 } 4178 } 4179 4180 rebased := (oldPrevRoot != md.PrevRoot()) 4181 if rebased { 4182 unmergedBID := md.BID() 4183 fbo.setBranchIDLocked(lState, unmergedBID) 4184 doResolve = true 4185 resolveMergedRev = kbfsmd.RevisionUninitialized 4186 } 4187 4188 fbo.headLock.Lock(lState) 4189 defer fbo.headLock.Unlock(lState) 4190 err = fbo.setHeadSuccessorLocked(ctx, lState, irmd, rebased) 4191 if err != nil { 4192 return err 4193 } 4194 4195 if TLFJournalEnabled(fbo.config, fbo.id()) { 4196 // Send unflushed notifications if journaling is on. 4197 err := fbo.handleUnflushedEditNotifications(ctx, irmd) 4198 if err != nil { 4199 fbo.log.CWarningf(ctx, "Couldn't send unflushed edit "+ 4200 "notifications for revision %d: %+v", irmd.Revision(), err) 4201 } 4202 } else { 4203 // Send edit notifications and archive the old, unref'd blocks 4204 // if journaling is off. 4205 fbo.editActivity.Add(1) 4206 fbo.vlog.CLogf( 4207 ctx, libkb.VLog1, "Sending notifications for %v", 4208 irmd.data.Changes.Ops) 4209 fbo.goTracked(func() { 4210 defer fbo.editActivity.Done() 4211 ctx, cancelFunc := fbo.newCtxWithFBOID() 4212 defer cancelFunc() 4213 err := fbo.handleEditNotifications(ctx, irmd) 4214 if err != nil { 4215 fbo.log.CWarningf(ctx, "Couldn't send edit notifications for "+ 4216 "revision %d: %+v", irmd.Revision(), err) 4217 } 4218 }) 4219 fbo.fbm.archiveUnrefBlocks(irmd.ReadOnly()) 4220 } 4221 4222 // Call Resolve() after the head is set, to make sure it fetches 4223 // the correct unmerged MD range during resolution. 4224 if doResolve { 4225 fbo.cr.Resolve(ctx, md.Revision(), resolveMergedRev) 4226 } 4227 4228 if notifyFn != nil { 4229 err := notifyFn(irmd) 4230 if err != nil { 4231 return err 4232 } 4233 } 4234 return nil 4235 } 4236 4237 func (fbo *folderBranchOps) waitForJournalLocked(ctx context.Context, 4238 lState *kbfssync.LockState, jManager *JournalManager) error { 4239 fbo.mdWriterLock.AssertLocked(lState) 4240 4241 if !TLFJournalEnabled(fbo.config, fbo.id()) { 4242 // Nothing to do. 4243 return nil 4244 } 4245 4246 if err := jManager.Wait(ctx, fbo.id()); err != nil { 4247 return err 4248 } 4249 4250 // Make sure everything flushed successfully, since we're holding 4251 // the writer lock, no other revisions could have snuck in. 4252 jStatus, err := jManager.JournalStatus(fbo.id()) 4253 if err != nil { 4254 return err 4255 } 4256 if jStatus.RevisionEnd != kbfsmd.RevisionUninitialized { 4257 return errors.Errorf("Couldn't flush all MD revisions; current "+ 4258 "revision end for the journal is %d", jStatus.RevisionEnd) 4259 } 4260 if jStatus.LastFlushErr != "" { 4261 return errors.Errorf("Couldn't flush the journal: %s", 4262 jStatus.LastFlushErr) 4263 } 4264 4265 return nil 4266 } 4267 4268 func (fbo *folderBranchOps) finalizeMDRekeyWriteLocked(ctx context.Context, 4269 lState *kbfssync.LockState, md *RootMetadata, 4270 lastWriterVerifyingKey kbfscrypto.VerifyingKey) (err error) { 4271 fbo.mdWriterLock.AssertLocked(lState) 4272 4273 oldPrevRoot := md.PrevRoot() 4274 4275 // Write out the new metadata. If journaling is enabled, we don't 4276 // want the rekey to hit the journal and possibly end up on a 4277 // conflict branch, so wait for the journal to flush and then push 4278 // straight to the server. TODO: we're holding the writer lock 4279 // while flushing the journal here (just like for exclusive 4280 // writes), which may end up blocking incoming writes for a long 4281 // time. Rekeys are pretty rare, but if this becomes an issue 4282 // maybe we should consider letting these hit the journal and 4283 // scrubbing them when converting it to a branch. 4284 mdOps := fbo.config.MDOps() 4285 if jManager, err := GetJournalManager(fbo.config); err == nil { 4286 if err = fbo.waitForJournalLocked(ctx, lState, jManager); err != nil { 4287 return err 4288 } 4289 mdOps = jManager.delegateMDOps 4290 } 4291 4292 var key kbfscrypto.VerifyingKey 4293 if md.IsWriterMetadataCopiedSet() { 4294 key = lastWriterVerifyingKey 4295 } else { 4296 var err error 4297 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 4298 if err != nil { 4299 return err 4300 } 4301 key = session.VerifyingKey 4302 } 4303 4304 irmd, err := mdOps.Put(ctx, md, key, nil, keybase1.MDPriorityNormal, nil) 4305 isConflict := isRevisionConflict(err) 4306 if err != nil && !isConflict { 4307 return err 4308 } 4309 4310 if isConflict { 4311 // Drop this block. We've probably collided with someone also 4312 // trying to rekey the same folder but that's not necessarily 4313 // the case. We'll queue another rekey just in case. It should 4314 // be safe as it's idempotent. We don't want any rekeys present 4315 // in unmerged history or that will just make a mess. 4316 fbo.config.RekeyQueue().Enqueue(md.TlfID()) 4317 return RekeyConflictError{err} 4318 } 4319 4320 fbo.setBranchIDLocked(lState, kbfsmd.NullBranchID) 4321 4322 rebased := (oldPrevRoot != md.PrevRoot()) 4323 if rebased { 4324 unmergedBID := md.BID() 4325 fbo.setBranchIDLocked(lState, unmergedBID) 4326 fbo.cr.Resolve(ctx, md.Revision(), kbfsmd.RevisionUninitialized) 4327 } 4328 4329 fbo.headLock.Lock(lState) 4330 defer fbo.headLock.Unlock(lState) 4331 err = fbo.setHeadSuccessorLocked(ctx, lState, irmd, rebased) 4332 if err != nil { 4333 return err 4334 } 4335 4336 // Explicitly set the latest merged revision, since if journaling 4337 // is on, `setHeadLocked` will not do it for us (even though 4338 // rekeys bypass the journal). 4339 fbo.setLatestMergedRevisionLocked(ctx, lState, md.Revision(), false) 4340 return nil 4341 } 4342 4343 func (fbo *folderBranchOps) finalizeGCOpLocked( 4344 ctx context.Context, lState *kbfssync.LockState, gco *GCOp) (err error) { 4345 fbo.mdWriterLock.AssertLocked(lState) 4346 4347 md, err := fbo.getSuccessorMDForWriteLocked(ctx, lState) 4348 if err != nil { 4349 return err 4350 } 4351 4352 if md.MergedStatus() == kbfsmd.Unmerged { 4353 return UnexpectedUnmergedPutError{} 4354 } 4355 4356 md.AddOp(gco) 4357 // TODO: if the revision number of this new commit is sequential 4358 // with `LatestRev`, we can probably change this to 4359 // `gco.LatestRev+1`. 4360 md.SetLastGCRevision(gco.LatestRev) 4361 4362 bps, err := fbo.maybeUnembedAndPutBlocks(ctx, md) 4363 if err != nil { 4364 return err 4365 } 4366 oldPrevRoot := md.PrevRoot() 4367 4368 err = fbo.finalizeBlocks(ctx, bps) 4369 if err != nil { 4370 return err 4371 } 4372 4373 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 4374 if err != nil { 4375 return err 4376 } 4377 4378 // finally, write out the new metadata 4379 irmd, err := fbo.config.MDOps().Put( 4380 ctx, md, session.VerifyingKey, nil, keybase1.MDPriorityNormal, bps) 4381 if err != nil { 4382 // Don't allow garbage collection to put us into a conflicting 4383 // state; just wait for the next period. 4384 return err 4385 } 4386 4387 fbo.setBranchIDLocked(lState, kbfsmd.NullBranchID) 4388 4389 rebased := (oldPrevRoot != md.PrevRoot()) 4390 if rebased { 4391 unmergedBID := md.BID() 4392 fbo.setBranchIDLocked(lState, unmergedBID) 4393 fbo.cr.Resolve(ctx, md.Revision(), kbfsmd.RevisionUninitialized) 4394 } 4395 4396 fbo.headLock.Lock(lState) 4397 defer fbo.headLock.Unlock(lState) 4398 err = fbo.setHeadSuccessorLocked(ctx, lState, irmd, rebased) 4399 if err != nil { 4400 return err 4401 } 4402 4403 return fbo.notifyBatchLocked(ctx, lState, irmd) 4404 } 4405 4406 func (fbo *folderBranchOps) finalizeGCOp(ctx context.Context, gco *GCOp) ( 4407 err error) { 4408 lState := makeFBOLockState() 4409 // Lock the folder so we can get an internally-consistent MD 4410 // revision number. 4411 fbo.mdWriterLock.Lock(lState) 4412 defer fbo.mdWriterLock.Unlock(lState) 4413 return fbo.finalizeGCOpLocked(ctx, lState, gco) 4414 } 4415 4416 // CtxAllowNameKeyType is the type for a context allowable name override key. 4417 type CtxAllowNameKeyType int 4418 4419 const ( 4420 // CtxAllowNameKey can be used to set a value in a context, and 4421 // that value will be treated as an allowable directory entry 4422 // name, even if it also matches a disallowed prefix. The value 4423 // must be of type `string`, or it will panic. 4424 CtxAllowNameKey CtxAllowNameKeyType = iota 4425 ) 4426 4427 func checkDisallowedPrefixes( 4428 ctx context.Context, name data.PathPartString) error { 4429 for _, prefix := range disallowedPrefixes { 4430 if strings.HasPrefix(name.Plaintext(), prefix) { 4431 if allowedName := ctx.Value(CtxAllowNameKey); allowedName != nil { 4432 // Allow specialized KBFS programs (like the kbgit remote 4433 // helper) to bypass the disallowed prefix check. 4434 if name.Plaintext() == allowedName.(string) { 4435 return nil 4436 } 4437 } 4438 return errors.WithStack(DisallowedPrefixError{name, prefix}) 4439 } 4440 } 4441 4442 // Don't allow any empty or `.` names. 4443 switch name.Plaintext() { 4444 case "", ".", "..": 4445 return errors.WithStack(DisallowedNameError{name.Plaintext()}) 4446 default: 4447 return nil 4448 } 4449 } 4450 4451 // PathType returns path type 4452 func (fbo *folderBranchOps) PathType() tlfhandle.PathType { 4453 switch fbo.folderBranch.Tlf.Type() { 4454 case tlf.Public: 4455 return tlfhandle.PublicPathType 4456 case tlf.Private: 4457 return tlfhandle.PrivatePathType 4458 case tlf.SingleTeam: 4459 return tlfhandle.SingleTeamPathType 4460 default: 4461 panic(fmt.Sprintf("Unknown TLF type: %s", fbo.folderBranch.Tlf.Type())) 4462 } 4463 } 4464 4465 // canonicalPath returns full canonical path for dir node and name. 4466 func (fbo *folderBranchOps) canonicalPathPlaintext( 4467 ctx context.Context, dir Node, name data.PathPartString) (string, error) { 4468 dirPath, err := fbo.pathFromNodeForRead(dir) 4469 if err != nil { 4470 return "", err 4471 } 4472 return tlfhandle.BuildCanonicalPath( 4473 fbo.PathType(), dirPath.Plaintext(), name.Plaintext()), nil 4474 } 4475 4476 func (fbo *folderBranchOps) signalWrite() { 4477 select { 4478 case fbo.syncNeededChan <- struct{}{}: 4479 default: 4480 } 4481 // A local write always means any ongoing CR should be canceled, 4482 // because the set of unmerged writes has changed. 4483 fbo.cr.ForceCancel() 4484 } 4485 4486 func (fbo *folderBranchOps) syncDirUpdateOrSignal( 4487 ctx context.Context, lState *kbfssync.LockState) error { 4488 if fbo.bType != standard { 4489 panic("Cannot write to a non-standard FBO") 4490 } 4491 if fbo.config.BGFlushDirOpBatchSize() == 1 { 4492 return fbo.syncAllLocked(ctx, lState, NoExcl) 4493 } 4494 fbo.signalWrite() 4495 return nil 4496 } 4497 4498 func (fbo *folderBranchOps) checkForUnlinkedDir(dir Node) error { 4499 // Disallow directory operations within an unlinked directory. 4500 // Shells don't seem to allow it, and it will just pollute the dir 4501 // entry cache with unsyncable entries. 4502 if fbo.nodeCache.IsUnlinked(dir) { 4503 dirPath := fbo.nodeCache.PathFromNode(dir).String() 4504 return errors.WithStack(UnsupportedOpInUnlinkedDirError{dirPath}) 4505 } 4506 return nil 4507 } 4508 4509 // entryType must not by Sym. 4510 func (fbo *folderBranchOps) createEntryLocked( 4511 ctx context.Context, lState *kbfssync.LockState, dir Node, 4512 name data.PathPartString, entryType data.EntryType, excl Excl) ( 4513 childNode Node, de data.DirEntry, err error) { 4514 fbo.mdWriterLock.AssertLocked(lState) 4515 4516 if err := checkDisallowedPrefixes(ctx, name); err != nil { 4517 return nil, data.DirEntry{}, err 4518 } 4519 4520 if uint32(len(name.Plaintext())) > fbo.config.MaxNameBytes() { 4521 return nil, data.DirEntry{}, 4522 NameTooLongError{name.String(), fbo.config.MaxNameBytes()} 4523 } 4524 4525 if err := fbo.checkForUnlinkedDir(dir); err != nil { 4526 return nil, data.DirEntry{}, err 4527 } 4528 4529 // Plaintext for notifications to the service. 4530 filename, err := fbo.canonicalPathPlaintext(ctx, dir, name) 4531 if err != nil { 4532 return nil, data.DirEntry{}, err 4533 } 4534 4535 // Verify we have permission to write (but don't make a successor yet). 4536 md, err := fbo.getMDForWriteLockedForFilename(ctx, lState, filename) 4537 if err != nil { 4538 return nil, data.DirEntry{}, err 4539 } 4540 4541 dirPath, err := fbo.pathFromNodeForMDWriteLocked(lState, dir) 4542 if err != nil { 4543 return nil, data.DirEntry{}, err 4544 } 4545 4546 // does name already exist? 4547 _, err = fbo.blocks.GetEntry( 4548 ctx, lState, md.ReadOnly(), dirPath.ChildPathNoPtr( 4549 name, fbo.makeObfuscator())) 4550 if err == nil { 4551 return nil, data.DirEntry{}, data.NameExistsError{Name: name.String()} 4552 } else if _, notExists := errors.Cause(err).(idutil.NoSuchNameError); !notExists { 4553 return nil, data.DirEntry{}, err 4554 } 4555 4556 parentPtr := dirPath.TailPointer() 4557 co, err := newCreateOp(name.Plaintext(), parentPtr, entryType) 4558 if err != nil { 4559 return nil, data.DirEntry{}, err 4560 } 4561 co.setFinalPath(dirPath) 4562 // create new data block 4563 var newBlock data.Block 4564 if entryType == data.Dir { 4565 newBlock = &data.DirBlock{ 4566 Children: make(map[string]data.DirEntry), 4567 } 4568 } else { 4569 newBlock = &data.FileBlock{} 4570 } 4571 4572 // Cache update and operations until batch happens. Make a new 4573 // temporary ID and directory entry. 4574 newID, err := fbo.config.cryptoPure().MakeTemporaryBlockID() 4575 if err != nil { 4576 return nil, data.DirEntry{}, err 4577 } 4578 4579 chargedTo, err := chargedToForTLF( 4580 ctx, fbo.config.KBPKI(), fbo.config.KBPKI(), fbo.config, 4581 md.GetTlfHandle()) 4582 if err != nil { 4583 return nil, data.DirEntry{}, err 4584 } 4585 4586 newPtr := data.BlockPointer{ 4587 ID: newID, 4588 KeyGen: md.LatestKeyGeneration(), 4589 DataVer: fbo.config.DataVersion(), 4590 DirectType: data.DirectBlock, 4591 Context: kbfsblock.MakeFirstContext( 4592 chargedTo, fbo.config.DefaultBlockType()), 4593 } 4594 co.AddRefBlock(newPtr) 4595 co.AddSelfUpdate(parentPtr) 4596 4597 node, err := fbo.nodeCache.GetOrCreate(newPtr, name, dir, entryType) 4598 if err != nil { 4599 return nil, data.DirEntry{}, err 4600 } 4601 4602 err = fbo.config.DirtyBlockCache().Put( 4603 ctx, fbo.id(), newPtr, fbo.branch(), newBlock) 4604 if err != nil { 4605 return nil, data.DirEntry{}, err 4606 } 4607 4608 now := fbo.nowUnixNano() 4609 de = data.DirEntry{ 4610 BlockInfo: data.BlockInfo{ 4611 BlockPointer: newPtr, 4612 EncodedSize: 0, 4613 }, 4614 EntryInfo: data.EntryInfo{ 4615 Type: entryType, 4616 Size: 0, 4617 Mtime: now, 4618 Ctime: now, 4619 }, 4620 } 4621 4622 // Set the TeamWriter for team TLFs, so we can return the 4623 // LastWriterUnverified before the writes are flushed from memory. 4624 if fbo.id().Type() == tlf.SingleTeam { 4625 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 4626 if err != nil { 4627 return nil, data.DirEntry{}, err 4628 } 4629 de.TeamWriter = session.UID 4630 } 4631 4632 dirCacheUndoFn, err := fbo.blocks.AddDirEntryInCache( 4633 ctx, lState, md.ReadOnly(), dirPath, name, de) 4634 if err != nil { 4635 return nil, data.DirEntry{}, err 4636 } 4637 fbo.dirOps = append(fbo.dirOps, cachedDirOp{co, []Node{dir, node}}) 4638 added := fbo.status.addDirtyNode(dir) 4639 4640 cleanupFn := func() { 4641 if added { 4642 fbo.status.rmDirtyNode(dir) 4643 } 4644 fbo.dirOps = fbo.dirOps[:len(fbo.dirOps)-1] 4645 if dirCacheUndoFn != nil { 4646 dirCacheUndoFn(lState) 4647 } 4648 // Delete should never fail. 4649 _ = fbo.config.DirtyBlockCache().Delete(fbo.id(), newPtr, fbo.branch()) 4650 } 4651 defer func() { 4652 if err != nil && cleanupFn != nil { 4653 cleanupFn() 4654 } 4655 }() 4656 4657 if entryType != data.Dir { 4658 // Dirty the file with a zero-byte write, to ensure the new 4659 // block is synced in SyncAll. TODO: remove this if we ever 4660 // embed 0-byte files in the directory entry itself. 4661 err = fbo.blocks.Write( 4662 ctx, lState, md.ReadOnly(), node, []byte{}, 0) 4663 if err != nil { 4664 return nil, data.DirEntry{}, err 4665 } 4666 oldCleanupFn := cleanupFn 4667 cleanupFn = func() { 4668 _ = fbo.blocks.ClearCacheInfo( 4669 lState, fbo.nodeCache.PathFromNode(node)) 4670 oldCleanupFn() 4671 } 4672 } 4673 4674 // It's safe to notify before we've synced, since it is only 4675 // sending invalidation notifications. At worst the upper layer 4676 // will just have to refresh its cache needlessly. 4677 err = fbo.notifyOneOp(ctx, lState, co, md.ReadOnly(), false) 4678 if err != nil { 4679 return nil, data.DirEntry{}, err 4680 } 4681 4682 if excl == WithExcl { 4683 // Sync this change to the server. 4684 err := fbo.syncAllLocked(ctx, lState, WithExcl) 4685 _, isNoUpdatesWhileDirty := errors.Cause(err).(NoUpdatesWhileDirtyError) 4686 if isNoUpdatesWhileDirty { 4687 // If an exclusive write hits a conflict, it will try to 4688 // update, but won't be able to because of the dirty 4689 // directory entries. We need to clean up the dirty 4690 // entries here first before trying to apply the updates 4691 // again. By returning `ExclOnUnmergedError` below, we 4692 // force the caller to retry the whole operation again. 4693 fbo.log.CDebugf(ctx, "Clearing dirty entry before applying new "+ 4694 "updates for exclusive write") 4695 cleanupFn() 4696 cleanupFn = nil 4697 4698 // Sync anything else that might be buffered (non-exclusively). 4699 err = fbo.syncAllLocked(ctx, lState, NoExcl) 4700 if err != nil { 4701 return nil, data.DirEntry{}, err 4702 } 4703 4704 // Now we should be in a clean state, so this should work. 4705 err = fbo.getAndApplyMDUpdates( 4706 ctx, lState, nil, fbo.applyMDUpdatesLocked) 4707 if err != nil { 4708 return nil, data.DirEntry{}, err 4709 } 4710 return nil, data.DirEntry{}, ExclOnUnmergedError{} 4711 } else if err != nil { 4712 return nil, data.DirEntry{}, err 4713 } 4714 } else { 4715 err = fbo.syncDirUpdateOrSignal(ctx, lState) 4716 if err != nil { 4717 return nil, data.DirEntry{}, err 4718 } 4719 } 4720 4721 return node, de, nil 4722 } 4723 4724 func (fbo *folderBranchOps) maybeWaitForSquash( 4725 ctx context.Context, unmergedBID kbfsmd.BranchID) { 4726 if unmergedBID != kbfsmd.PendingLocalSquashBranchID { 4727 return 4728 } 4729 4730 fbo.vlog.CLogf(ctx, libkb.VLog1, "Blocking until squash finishes") 4731 // Limit the time we wait to just under the ctx deadline if there 4732 // is one, or 10s if there isn't. 4733 deadline, ok := ctx.Deadline() 4734 if ok { 4735 deadline = deadline.Add(-1 * time.Second) 4736 } else { 4737 // Can't use config.Clock() since context doesn't respect it. 4738 deadline = time.Now().Add(10 * time.Second) 4739 } 4740 ctx, cancel := context.WithDeadline(ctx, deadline) 4741 defer cancel() 4742 // Wait for CR to finish. Note that if the user is issuing 4743 // concurrent writes, the current CR could be canceled, and when 4744 // the call belows returns, the branch still won't be squashed. 4745 // That's ok, this is just an optimization. 4746 err := fbo.cr.Wait(ctx) 4747 if err != nil { 4748 fbo.log.CDebugf(ctx, "Error while waiting for CR: %+v", err) 4749 } 4750 } 4751 4752 func (fbo *folderBranchOps) doMDWriteWithRetry(ctx context.Context, 4753 lState *kbfssync.LockState, 4754 fn func(lState *kbfssync.LockState) error) error { 4755 doUnlock := false 4756 defer func() { 4757 if doUnlock { 4758 unmergedBID := fbo.unmergedBID 4759 fbo.mdWriterLock.Unlock(lState) 4760 // Don't let a pending squash get too big. 4761 fbo.maybeWaitForSquash(ctx, unmergedBID) 4762 } 4763 }() 4764 4765 for i := 0; ; i++ { 4766 fbo.mdWriterLock.Lock(lState) 4767 doUnlock = true 4768 4769 // Make sure we haven't been canceled before doing anything 4770 // too serious. 4771 select { 4772 case <-ctx.Done(): 4773 return ctx.Err() 4774 default: 4775 } 4776 4777 err := fn(lState) 4778 if isRetriableError(err, i) { 4779 fbo.log.CDebugf(ctx, "Trying again after retriable error: %v", err) 4780 // Release the lock to give someone else a chance 4781 doUnlock = false 4782 fbo.mdWriterLock.Unlock(lState) 4783 if _, ok := err.(ExclOnUnmergedError); ok { 4784 if err = fbo.cr.Wait(ctx); err != nil { 4785 return err 4786 } 4787 } else if _, ok := err.(UnmergedSelfConflictError); ok { 4788 // We can only get here if we are already on an 4789 // unmerged branch and an errored PutUnmerged did make 4790 // it to the mdserver. Let's force sync, with a fresh 4791 // context so the observer doesn't ignore the updates 4792 // (but tie the cancels together). 4793 newCtx := fbo.ctxWithFBOID(context.Background()) 4794 newCtx, cancel := context.WithCancel(newCtx) 4795 defer cancel() 4796 fbo.goTracked(func() { 4797 select { 4798 case <-ctx.Done(): 4799 cancel() 4800 case <-newCtx.Done(): 4801 } 4802 }) 4803 fbo.vlog.CLogf( 4804 ctx, libkb.VLog1, "Got a revision conflict while unmerged "+ 4805 "(%v); forcing a sync", err) 4806 err = fbo.getAndApplyNewestUnmergedHead(newCtx, lState) 4807 if err != nil { 4808 // TODO: we might be stuck at this point if we're 4809 // ahead of the unmerged branch on the server, in 4810 // which case we might want to just abandon any 4811 // cached updates and force a sync to the head. 4812 return err 4813 } 4814 cancel() 4815 } 4816 continue 4817 } else if err != nil { 4818 return err 4819 } 4820 return nil 4821 } 4822 } 4823 4824 func (fbo *folderBranchOps) doMDWriteWithRetryUnlessCanceled( 4825 ctx context.Context, fn func(lState *kbfssync.LockState) error) error { 4826 return runUnlessCanceled(ctx, func() error { 4827 lState := makeFBOLockState() 4828 return fbo.doMDWriteWithRetry(ctx, lState, fn) 4829 }) 4830 } 4831 4832 func (fbo *folderBranchOps) CreateDir( 4833 ctx context.Context, dir Node, path data.PathPartString) ( 4834 n Node, ei data.EntryInfo, err error) { 4835 startTime, timer := fbo.startOp( 4836 ctx, "CreateDir %s %s", getNodeIDStr(dir), path) 4837 defer func() { 4838 fbo.endOp( 4839 ctx, startTime, timer, "CreateDir %s %s done: %v %+v", 4840 getNodeIDStr(dir), path, getNodeIDStr(n), err) 4841 }() 4842 4843 err = fbo.checkNodeForWrite(ctx, dir) 4844 if err != nil { 4845 return nil, data.EntryInfo{}, err 4846 } 4847 4848 var retNode Node 4849 var retEntryInfo data.EntryInfo 4850 err = fbo.doMDWriteWithRetryUnlessCanceled(ctx, 4851 func(lState *kbfssync.LockState) error { 4852 node, de, err := 4853 fbo.createEntryLocked(ctx, lState, dir, path, data.Dir, NoExcl) 4854 // Don't set node and ei directly, as that can cause a 4855 // race when the Create is canceled. 4856 retNode = node 4857 retEntryInfo = de.EntryInfo 4858 return err 4859 }) 4860 if err != nil { 4861 return nil, data.EntryInfo{}, err 4862 } 4863 return retNode, retEntryInfo, nil 4864 } 4865 4866 func (fbo *folderBranchOps) CreateFile( 4867 ctx context.Context, dir Node, path data.PathPartString, isExec bool, 4868 excl Excl) (n Node, ei data.EntryInfo, err error) { 4869 startTime, timer := fbo.startOp( 4870 ctx, "CreateFile %s %s isExec=%v Excl=%s", getNodeIDStr(dir), 4871 path, isExec, excl) 4872 defer func() { 4873 fbo.endOp( 4874 ctx, startTime, timer, 4875 "CreateFile %s %s isExec=%v Excl=%s done: %v %+v", 4876 getNodeIDStr(dir), path, isExec, excl, 4877 getNodeIDStr(n), err) 4878 }() 4879 4880 err = fbo.checkNodeForWrite(ctx, dir) 4881 if err != nil { 4882 return nil, data.EntryInfo{}, err 4883 } 4884 4885 var entryType data.EntryType 4886 if isExec { 4887 entryType = data.Exec 4888 } else { 4889 entryType = data.File 4890 } 4891 4892 // If journaling is turned on, an exclusive create may end up on a 4893 // conflict branch. 4894 if excl == WithExcl && TLFJournalEnabled(fbo.config, fbo.id()) { 4895 fbo.vlog.CLogf( 4896 ctx, libkb.VLog1, "Exclusive create status is being discarded.") 4897 excl = NoExcl 4898 } 4899 4900 if excl == WithExcl { 4901 if err = fbo.cr.Wait(ctx); err != nil { 4902 return nil, data.EntryInfo{}, err 4903 } 4904 } 4905 4906 var retNode Node 4907 var retEntryInfo data.EntryInfo 4908 err = fbo.doMDWriteWithRetryUnlessCanceled(ctx, 4909 func(lState *kbfssync.LockState) error { 4910 // Don't set node and ei directly, as that can cause a 4911 // race when the Create is canceled. 4912 node, de, err := 4913 fbo.createEntryLocked(ctx, lState, dir, path, entryType, excl) 4914 retNode = node 4915 retEntryInfo = de.EntryInfo 4916 return err 4917 }) 4918 if err != nil { 4919 return nil, data.EntryInfo{}, err 4920 } 4921 return retNode, retEntryInfo, nil 4922 } 4923 4924 // notifyAndSyncOrSignal caches an op in memory and dirties the 4925 // relevant node, and then sends a notification for it. If batching 4926 // is on, it signals the write; otherwise it syncs the change. It 4927 // should only be called as the final instruction that can fail in a 4928 // method. 4929 func (fbo *folderBranchOps) notifyAndSyncOrSignal( 4930 ctx context.Context, lState *kbfssync.LockState, undoFn dirCacheUndoFn, 4931 nodesToDirty []Node, op op, md ReadOnlyRootMetadata) (err error) { 4932 fbo.dirOps = append(fbo.dirOps, cachedDirOp{op, nodesToDirty}) 4933 var addedNodes []Node 4934 for _, n := range nodesToDirty { 4935 added := fbo.status.addDirtyNode(n) 4936 if added { 4937 addedNodes = append(addedNodes, n) 4938 } 4939 } 4940 4941 defer func() { 4942 if err != nil { 4943 for _, n := range addedNodes { 4944 fbo.status.rmDirtyNode(n) 4945 } 4946 fbo.dirOps = fbo.dirOps[:len(fbo.dirOps)-1] 4947 if undoFn != nil { 4948 undoFn(lState) 4949 } 4950 } 4951 }() 4952 4953 // It's safe to notify before we've synced, since it is only 4954 // sending invalidation notifications. At worst the upper layer 4955 // will just have to refresh its cache needlessly. 4956 err = fbo.notifyOneOp(ctx, lState, op, md, false) 4957 if err != nil { 4958 return err 4959 } 4960 4961 return fbo.syncDirUpdateOrSignal(ctx, lState) 4962 } 4963 4964 func (fbo *folderBranchOps) createLinkLocked( 4965 ctx context.Context, lState *kbfssync.LockState, dir Node, 4966 fromName, toPath data.PathPartString) (data.DirEntry, error) { 4967 fbo.mdWriterLock.AssertLocked(lState) 4968 4969 if err := checkDisallowedPrefixes(ctx, fromName); err != nil { 4970 return data.DirEntry{}, err 4971 } 4972 4973 if uint32(len(fromName.Plaintext())) > fbo.config.MaxNameBytes() { 4974 return data.DirEntry{}, 4975 NameTooLongError{fromName.Plaintext(), fbo.config.MaxNameBytes()} 4976 } 4977 4978 if err := fbo.checkForUnlinkedDir(dir); err != nil { 4979 return data.DirEntry{}, err 4980 } 4981 4982 // Verify we have permission to write (but don't make a successor yet). 4983 md, err := fbo.getMDForWriteLockedForFilename(ctx, lState, "") 4984 if err != nil { 4985 return data.DirEntry{}, err 4986 } 4987 4988 dirPath, err := fbo.pathFromNodeForMDWriteLocked(lState, dir) 4989 if err != nil { 4990 return data.DirEntry{}, err 4991 } 4992 4993 // TODO: validate inputs 4994 4995 // does name already exist? 4996 _, err = fbo.blocks.GetEntry( 4997 ctx, lState, md.ReadOnly(), dirPath.ChildPathNoPtr( 4998 fromName, fbo.makeObfuscator())) 4999 if err == nil { 5000 return data.DirEntry{}, data.NameExistsError{Name: fromName.String()} 5001 } else if _, notExists := errors.Cause(err).(idutil.NoSuchNameError); !notExists { 5002 return data.DirEntry{}, err 5003 } 5004 5005 parentPtr := dirPath.TailPointer() 5006 co, err := newCreateOp(fromName.Plaintext(), parentPtr, data.Sym) 5007 if err != nil { 5008 return data.DirEntry{}, err 5009 } 5010 co.setFinalPath(dirPath) 5011 co.AddSelfUpdate(parentPtr) 5012 5013 // Nothing below here can fail, so no need to clean up the dir 5014 // entry cache on a failure. If this ever panics, we need to add 5015 // cleanup code. 5016 5017 // Create a direntry for the link, and then sync 5018 now := fbo.nowUnixNano() 5019 toPathPlain := toPath.Plaintext() 5020 de := data.DirEntry{ 5021 EntryInfo: data.EntryInfo{ 5022 Type: data.Sym, 5023 Size: uint64(len(toPathPlain)), 5024 SymPath: toPathPlain, 5025 Mtime: now, 5026 Ctime: now, 5027 }, 5028 } 5029 5030 dirCacheUndoFn, err := fbo.blocks.AddDirEntryInCache( 5031 ctx, lState, md.ReadOnly(), dirPath, fromName, de) 5032 if err != nil { 5033 return data.DirEntry{}, err 5034 } 5035 5036 err = fbo.notifyAndSyncOrSignal( 5037 ctx, lState, dirCacheUndoFn, []Node{dir}, co, md.ReadOnly()) 5038 if err != nil { 5039 return data.DirEntry{}, err 5040 } 5041 return de, nil 5042 } 5043 5044 func (fbo *folderBranchOps) CreateLink( 5045 ctx context.Context, dir Node, fromName, toPath data.PathPartString) ( 5046 ei data.EntryInfo, err error) { 5047 startTime, timer := fbo.startOp(ctx, "CreateLink %s %s -> %s", 5048 getNodeIDStr(dir), fromName, toPath) 5049 defer func() { 5050 fbo.endOp( 5051 ctx, startTime, timer, "CreateLink %s %s -> %s done: %+v", 5052 getNodeIDStr(dir), fromName, toPath, err) 5053 }() 5054 5055 err = fbo.checkNodeForWrite(ctx, dir) 5056 if err != nil { 5057 return data.EntryInfo{}, err 5058 } 5059 5060 var retEntryInfo data.EntryInfo 5061 err = fbo.doMDWriteWithRetryUnlessCanceled(ctx, 5062 func(lState *kbfssync.LockState) error { 5063 // Don't set ei directly, as that can cause a race when 5064 // the Create is canceled. 5065 de, err := fbo.createLinkLocked(ctx, lState, dir, fromName, toPath) 5066 retEntryInfo = de.EntryInfo 5067 return err 5068 }) 5069 if err != nil { 5070 return data.EntryInfo{}, err 5071 } 5072 return retEntryInfo, nil 5073 } 5074 5075 // unrefEntry modifies md to unreference all relevant blocks for the 5076 // given entry. 5077 func (fbo *folderBranchOps) unrefEntryLocked(ctx context.Context, 5078 lState *kbfssync.LockState, kmd libkey.KeyMetadata, ro op, dir data.Path, 5079 de data.DirEntry, name data.PathPartString) error { 5080 fbo.mdWriterLock.AssertLocked(lState) 5081 if de.Type == data.Sym { 5082 return nil 5083 } 5084 5085 unrefsToAdd := make(map[data.BlockPointer]bool) 5086 fbo.prepper.cacheBlockInfos([]data.BlockInfo{de.BlockInfo}) 5087 unrefsToAdd[de.BlockPointer] = true 5088 // construct a path for the child so we can unlink with it. 5089 childPath := dir.ChildPath( 5090 name, de.BlockPointer, fbo.makeObfuscator()) 5091 5092 // If this is an indirect block, we need to delete all of its 5093 // children as well. NOTE: non-empty directories can't be 5094 // removed, so no need to check for indirect directory blocks 5095 // here. 5096 if de.Type == data.File || de.Type == data.Exec { 5097 blockInfos, err := fbo.blocks.GetIndirectFileBlockInfos( 5098 ctx, lState, kmd, childPath) 5099 if isRecoverableBlockErrorForRemoval(err) { 5100 msg := fmt.Sprintf("Recoverable block error encountered for unrefEntry(%v); continuing", childPath) 5101 fbo.log.CWarningf(ctx, "%s", msg) 5102 fbo.log.CDebugf(ctx, "%s (err=%v)", msg, err) 5103 } else if err != nil { 5104 return err 5105 } 5106 fbo.prepper.cacheBlockInfos(blockInfos) 5107 for _, blockInfo := range blockInfos { 5108 unrefsToAdd[blockInfo.BlockPointer] = true 5109 } 5110 } 5111 5112 // Any referenced blocks that were unreferenced since the last 5113 // sync can just be forgotten about. Note that any updated 5114 // pointers that are unreferenced will be fixed up during syncing. 5115 for _, dirOp := range fbo.dirOps { 5116 for i := len(dirOp.dirOp.Refs()) - 1; i >= 0; i-- { 5117 ref := dirOp.dirOp.Refs()[i] 5118 if _, ok := unrefsToAdd[ref]; ok { 5119 dirOp.dirOp.DelRefBlock(ref) 5120 delete(unrefsToAdd, ref) 5121 } 5122 } 5123 } 5124 for unref := range unrefsToAdd { 5125 ro.AddUnrefBlock(unref) 5126 } 5127 5128 return nil 5129 } 5130 5131 func (fbo *folderBranchOps) removeEntryLocked(ctx context.Context, 5132 lState *kbfssync.LockState, md ReadOnlyRootMetadata, dir Node, 5133 dirPath data.Path, name data.PathPartString) error { 5134 fbo.mdWriterLock.AssertLocked(lState) 5135 5136 if err := fbo.checkForUnlinkedDir(dir); err != nil { 5137 return err 5138 } 5139 5140 // make sure the entry exists 5141 de, err := fbo.blocks.GetEntry( 5142 ctx, lState, md, dirPath.ChildPathNoPtr( 5143 name, fbo.makeObfuscator())) 5144 if _, notExists := errors.Cause(err).(idutil.NoSuchNameError); notExists { 5145 return idutil.NoSuchNameError{Name: name.String()} 5146 } else if err != nil { 5147 return err 5148 } 5149 5150 parentPtr := dirPath.TailPointer() 5151 ro, err := newRmOp(name.Plaintext(), parentPtr, de.Type) 5152 if err != nil { 5153 return err 5154 } 5155 ro.setFinalPath(dirPath) 5156 ro.AddSelfUpdate(parentPtr) 5157 err = fbo.unrefEntryLocked(ctx, lState, md, ro, dirPath, de, name) 5158 if err != nil { 5159 return err 5160 } 5161 5162 dirCacheUndoFn, err := fbo.blocks.RemoveDirEntryInCache( 5163 ctx, lState, md.ReadOnly(), dirPath, name, de) 5164 if err != nil { 5165 return err 5166 } 5167 if de.Type == data.Dir { 5168 removedNode := fbo.nodeCache.Get(de.BlockPointer.Ref()) 5169 if removedNode != nil { 5170 // If it was a dirty directory, the removed node no longer 5171 // counts as dirty (it will never be sync'd). Note that 5172 // removed files will still be synced since any data 5173 // written to them via a handle stays in memory until the 5174 // sync actually happens. 5175 removed := fbo.status.rmDirtyNode(removedNode) 5176 if removed { 5177 oldUndoFn := dirCacheUndoFn 5178 dirCacheUndoFn = func(lState *kbfssync.LockState) { 5179 oldUndoFn(lState) 5180 fbo.status.addDirtyNode(removedNode) 5181 } 5182 } 5183 } 5184 } 5185 return fbo.notifyAndSyncOrSignal( 5186 ctx, lState, dirCacheUndoFn, []Node{dir}, ro, md.ReadOnly()) 5187 } 5188 5189 func (fbo *folderBranchOps) removeDirLocked(ctx context.Context, 5190 lState *kbfssync.LockState, dir Node, dirName data.PathPartString) ( 5191 err error) { 5192 fbo.mdWriterLock.AssertLocked(lState) 5193 5194 // Verify we have permission to write (but don't make a successor yet). 5195 md, err := fbo.getMDForWriteLockedForFilename(ctx, lState, "") 5196 if err != nil { 5197 return err 5198 } 5199 5200 dirPath, err := fbo.pathFromNodeForMDWriteLocked(lState, dir) 5201 if err != nil { 5202 return err 5203 } 5204 5205 ob := fbo.makeObfuscator() 5206 de, err := fbo.blocks.GetEntry( 5207 ctx, lState, md.ReadOnly(), dirPath.ChildPathNoPtr(dirName, ob)) 5208 if _, notExists := errors.Cause(err).(idutil.NoSuchNameError); notExists { 5209 return idutil.NoSuchNameError{Name: dirName.String()} 5210 } else if err != nil { 5211 return err 5212 } 5213 5214 // construct a path for the child so we can check for an empty dir 5215 childPath := dirPath.ChildPath(dirName, de.BlockPointer, ob) 5216 5217 // Note this fetches all the blocks associated with this 5218 // directory, even though technically we just need to find one 5219 // entry and it might be wasteful to fetch all the blocks. 5220 // However, since removals don't reduce levels of indirection at 5221 // the moment, we're forced to do this for now. 5222 entries, err := fbo.blocks.GetEntries(ctx, lState, md.ReadOnly(), childPath) 5223 switch { 5224 case isRecoverableBlockErrorForRemoval(err): 5225 msg := fmt.Sprintf("Recoverable block error encountered for removeDirLocked(%v); continuing", childPath) 5226 fbo.log.CWarningf(ctx, "%s", msg) 5227 fbo.log.CDebugf(ctx, "%s (err=%v)", msg, err) 5228 case err != nil: 5229 return err 5230 case len(entries) > 0: 5231 return DirNotEmptyError{dirName} 5232 } 5233 5234 return fbo.removeEntryLocked( 5235 ctx, lState, md.ReadOnly(), dir, dirPath, dirName) 5236 } 5237 5238 func (fbo *folderBranchOps) RemoveDir( 5239 ctx context.Context, dir Node, dirName data.PathPartString) (err error) { 5240 startTime, timer := fbo.startOp( 5241 ctx, "RemoveDir %s %s", getNodeIDStr(dir), dirName) 5242 defer func() { 5243 fbo.endOp( 5244 ctx, startTime, timer, "RemoveDir %s %s done: %+v", 5245 getNodeIDStr(dir), dirName, err) 5246 }() 5247 5248 removeDone, err := dir.RemoveDir(ctx, dirName) 5249 if err != nil { 5250 return err 5251 } 5252 if removeDone { 5253 return nil 5254 } 5255 5256 err = fbo.checkNodeForWrite(ctx, dir) 5257 if err != nil { 5258 return err 5259 } 5260 5261 return fbo.doMDWriteWithRetryUnlessCanceled(ctx, 5262 func(lState *kbfssync.LockState) error { 5263 return fbo.removeDirLocked(ctx, lState, dir, dirName) 5264 }) 5265 } 5266 5267 func (fbo *folderBranchOps) RemoveEntry(ctx context.Context, dir Node, 5268 name data.PathPartString) (err error) { 5269 startTime, timer := fbo.startOp( 5270 ctx, "RemoveEntry %s %s", getNodeIDStr(dir), name) 5271 defer func() { 5272 fbo.endOp( 5273 ctx, startTime, timer, "RemoveEntry %s %s done: %+v", 5274 getNodeIDStr(dir), name, err) 5275 }() 5276 5277 err = fbo.checkNodeForWrite(ctx, dir) 5278 if err != nil { 5279 return err 5280 } 5281 5282 return fbo.doMDWriteWithRetryUnlessCanceled(ctx, 5283 func(lState *kbfssync.LockState) error { 5284 // Verify we have permission to write (but no need to make 5285 // a successor yet). 5286 md, err := fbo.getMDForWriteLockedForFilename(ctx, lState, "") 5287 if err != nil { 5288 return err 5289 } 5290 5291 dirPath, err := fbo.pathFromNodeForMDWriteLocked(lState, dir) 5292 if err != nil { 5293 return err 5294 } 5295 5296 return fbo.removeEntryLocked( 5297 ctx, lState, md.ReadOnly(), dir, dirPath, name) 5298 }) 5299 } 5300 5301 func (fbo *folderBranchOps) renameLocked( 5302 ctx context.Context, lState *kbfssync.LockState, oldParent Node, 5303 oldName data.PathPartString, newParent Node, newName data.PathPartString) ( 5304 err error) { 5305 fbo.mdWriterLock.AssertLocked(lState) 5306 5307 if err := fbo.checkForUnlinkedDir(oldParent); err != nil { 5308 return err 5309 } 5310 if err := fbo.checkForUnlinkedDir(newParent); err != nil { 5311 return err 5312 } 5313 5314 if err := checkDisallowedPrefixes(ctx, newName); err != nil { 5315 return err 5316 } 5317 5318 oldParentPath, err := fbo.pathFromNodeForMDWriteLocked(lState, oldParent) 5319 if err != nil { 5320 return err 5321 } 5322 5323 newParentPath, err := fbo.pathFromNodeForMDWriteLocked(lState, newParent) 5324 if err != nil { 5325 return err 5326 } 5327 5328 // Verify we have permission to write (but no need to make a 5329 // successor yet). 5330 md, err := fbo.getMDForWriteLockedForFilename(ctx, lState, "") 5331 if err != nil { 5332 return err 5333 } 5334 5335 newDe, replacedDe, ro, err := fbo.blocks.PrepRename( 5336 ctx, lState, md.ReadOnly(), oldParentPath, oldName, newParentPath, 5337 newName) 5338 if err != nil { 5339 return err 5340 } 5341 5342 // does name exist? 5343 if replacedDe.IsInitialized() { 5344 // Usually higher-level programs check these, but just in case. 5345 ob := fbo.makeObfuscator() 5346 if replacedDe.Type == data.Dir && newDe.Type != data.Dir { 5347 return NotDirError{newParentPath.ChildPathNoPtr(newName, ob)} 5348 } else if replacedDe.Type != data.Dir && newDe.Type == data.Dir { 5349 return NotFileError{newParentPath.ChildPathNoPtr(newName, ob)} 5350 } 5351 5352 if replacedDe.Type == data.Dir { 5353 // The directory must be empty. 5354 entries, err := fbo.blocks.GetEntries( 5355 ctx, lState, md.ReadOnly(), 5356 newParentPath.ChildPath(newName, replacedDe.BlockPointer, ob)) 5357 if err != nil { 5358 return err 5359 } 5360 if len(entries) != 0 { 5361 fbo.log.CWarningf(ctx, "Renaming over a non-empty directory "+ 5362 " (%s/%s) not allowed.", newParentPath, newName) 5363 return DirNotEmptyError{newName} 5364 } 5365 } 5366 5367 // Delete the old block pointed to by this direntry. 5368 err := fbo.unrefEntryLocked( 5369 ctx, lState, md.ReadOnly(), ro, newParentPath, replacedDe, newName) 5370 if err != nil { 5371 return err 5372 } 5373 } 5374 5375 // Only the ctime changes on the directory entry itself. 5376 newDe.Ctime = fbo.nowUnixNano() 5377 5378 dirCacheUndoFn, err := fbo.blocks.RenameDirEntryInCache( 5379 ctx, lState, md.ReadOnly(), oldParentPath, oldName, newParentPath, 5380 newName, newDe, replacedDe) 5381 if err != nil { 5382 return err 5383 } 5384 5385 nodesToDirty := []Node{oldParent} 5386 if oldParent.GetID() != newParent.GetID() { 5387 nodesToDirty = append(nodesToDirty, newParent) 5388 } 5389 return fbo.notifyAndSyncOrSignal( 5390 ctx, lState, dirCacheUndoFn, nodesToDirty, ro, md.ReadOnly()) 5391 } 5392 5393 func (fbo *folderBranchOps) Rename( 5394 ctx context.Context, oldParent Node, oldName data.PathPartString, 5395 newParent Node, newName data.PathPartString) (err error) { 5396 startTime, timer := fbo.startOp( 5397 ctx, "Rename %s/%s -> %s/%s", getNodeIDStr(oldParent), 5398 oldName, getNodeIDStr(newParent), newName) 5399 defer func() { 5400 fbo.endOp( 5401 ctx, startTime, timer, "Rename %s/%s -> %s/%s done: %+v", 5402 getNodeIDStr(oldParent), oldName, 5403 getNodeIDStr(newParent), newName, err) 5404 }() 5405 5406 err = fbo.checkNodeForWrite(ctx, oldParent) 5407 if err != nil { 5408 return err 5409 } 5410 err = fbo.checkNodeForWrite(ctx, newParent) 5411 if err != nil { 5412 return err 5413 } 5414 5415 return fbo.doMDWriteWithRetryUnlessCanceled(ctx, 5416 func(lState *kbfssync.LockState) error { 5417 // only works for paths within the same topdir 5418 if oldParent.GetFolderBranch() != newParent.GetFolderBranch() { 5419 return RenameAcrossDirsError{} 5420 } 5421 5422 return fbo.renameLocked(ctx, lState, oldParent, oldName, 5423 newParent, newName) 5424 }) 5425 } 5426 5427 func (fbo *folderBranchOps) Read( 5428 ctx context.Context, file Node, dest []byte, off int64) ( 5429 n int64, err error) { 5430 startTime, timer := fbo.startOp( 5431 ctx, "Read %s %d %d", getNodeIDStr(file), len(dest), off) 5432 defer func() { 5433 err = fbo.transformReadError(ctx, file, err) 5434 fbo.endOp( 5435 ctx, startTime, timer, "Read %s %d %d (n=%d) done: %+v", 5436 getNodeIDStr(file), len(dest), off, n, err) 5437 }() 5438 5439 err = fbo.checkNodeForRead(ctx, file) 5440 if err != nil { 5441 return 0, err 5442 } 5443 5444 fsFile := file.GetFile(ctx) 5445 if fsFile != nil { 5446 defer fsFile.Close() 5447 fbo.vlog.CLogf(ctx, libkb.VLog1, "Reading from an FS file") 5448 nInt, err := fsFile.ReadAt(dest, off) 5449 if nInt == 0 && errors.Cause(err) == io.EOF { 5450 // The billy interfaces requires an EOF when you start 5451 // reading past the end of a file, but the libkbfs 5452 // interface wants a nil error in that case. 5453 err = nil 5454 } 5455 return int64(nInt), err 5456 } 5457 5458 { 5459 filePath, err := fbo.pathFromNodeForRead(file) 5460 if err != nil { 5461 return 0, err 5462 } 5463 5464 // It seems git isn't handling EINTR from some of its read calls (likely 5465 // fread), which causes it to get corrupted data (which leads to coredumps 5466 // later) when a read system call on pack files gets interrupted. This 5467 // enables delayed cancellation for Read if the file path contains `.git`. 5468 // 5469 // TODO: get a patch in git, wait for sufficiently long time for people to 5470 // upgrade, and remove this. 5471 5472 // allow turning this feature off by env var to make life easier when we 5473 // try to fix git. 5474 if _, isSet := os.LookupEnv("KBFS_DISABLE_GIT_SPECIAL_CASE"); !isSet { 5475 for _, n := range filePath.Path { 5476 if n.Name.Plaintext() == ".git" { 5477 _ = libcontext.EnableDelayedCancellationWithGracePeriod( 5478 ctx, fbo.config.DelayedCancellationGracePeriod()) 5479 break 5480 } 5481 } 5482 } 5483 } 5484 5485 // Don't let the goroutine below write directly to the return 5486 // variable, since if the context is canceled the goroutine might 5487 // outlast this function call, and end up in a read/write race 5488 // with the caller. 5489 var bytesRead int64 5490 err = runUnlessCanceled(ctx, func() error { 5491 lState := makeFBOLockState() 5492 5493 // verify we have permission to read 5494 md, err := fbo.getMDForReadNeedIdentify(ctx, lState) 5495 if err != nil { 5496 return err 5497 } 5498 5499 // Read using the `file` Node, not `filePath`, since the path 5500 // could change until we take `blockLock` for reading. 5501 bytesRead, err = fbo.blocks.Read( 5502 ctx, lState, md.ReadOnly(), file, dest, off) 5503 return err 5504 }) 5505 if err != nil { 5506 return 0, err 5507 } 5508 return bytesRead, nil 5509 } 5510 5511 func (fbo *folderBranchOps) Write( 5512 ctx context.Context, file Node, data []byte, off int64) (err error) { 5513 startTime, timer := fbo.startOp( 5514 ctx, "Write %s %d %d", getNodeIDStr(file), len(data), off) 5515 defer func() { 5516 fbo.endOp( 5517 ctx, startTime, timer, "Write %s %d %d done: %+v", 5518 getNodeIDStr(file), len(data), off, err) 5519 }() 5520 5521 err = fbo.checkNodeForWrite(ctx, file) 5522 if err != nil { 5523 return err 5524 } 5525 5526 return runUnlessCanceled(ctx, func() error { 5527 lState := makeFBOLockState() 5528 5529 // Get the MD for reading. We won't modify it; we'll track the 5530 // unref changes on the side, and put them into the MD during the 5531 // sync. 5532 md, err := fbo.getMDForRead(ctx, lState, mdReadNeedIdentify) 5533 if err != nil { 5534 return err 5535 } 5536 5537 err = fbo.blocks.Write( 5538 ctx, lState, md.ReadOnly(), file, data, off) 5539 if err != nil { 5540 return err 5541 } 5542 5543 fbo.status.addDirtyNode(file) 5544 fbo.signalWrite() 5545 return nil 5546 }) 5547 } 5548 5549 func (fbo *folderBranchOps) Truncate( 5550 ctx context.Context, file Node, size uint64) (err error) { 5551 startTime, timer := fbo.startOp( 5552 ctx, "Truncate %s %d", getNodeIDStr(file), size) 5553 defer func() { 5554 fbo.endOp( 5555 ctx, startTime, timer, "Truncate %s %d done: %+v", 5556 getNodeIDStr(file), size, err) 5557 }() 5558 5559 err = fbo.checkNodeForWrite(ctx, file) 5560 if err != nil { 5561 return err 5562 } 5563 5564 return runUnlessCanceled(ctx, func() error { 5565 lState := makeFBOLockState() 5566 5567 // Get the MD for reading. We won't modify it; we'll track the 5568 // unref changes on the side, and put them into the MD during the 5569 // sync. 5570 md, err := fbo.getMDForRead(ctx, lState, mdReadNeedIdentify) 5571 if err != nil { 5572 return err 5573 } 5574 5575 err = fbo.blocks.Truncate( 5576 ctx, lState, md.ReadOnly(), file, size) 5577 if err != nil { 5578 return err 5579 } 5580 5581 filePath, err := fbo.pathFromNodeForRead(file) 5582 if err != nil { 5583 return err 5584 } 5585 5586 // Only mark the path as dirty if it was actually changed. 5587 if fbo.blocks.IsDirty(lState, filePath) { 5588 fbo.status.addDirtyNode(file) 5589 } 5590 fbo.signalWrite() 5591 return nil 5592 }) 5593 } 5594 5595 func (fbo *folderBranchOps) setExLocked( 5596 ctx context.Context, lState *kbfssync.LockState, file Node, ex bool) ( 5597 err error) { 5598 fbo.mdWriterLock.AssertLocked(lState) 5599 5600 filePath, err := fbo.pathFromNodeForMDWriteLocked(lState, file) 5601 if err != nil { 5602 return err 5603 } 5604 5605 if !filePath.HasValidParent() { 5606 return InvalidParentPathError{filePath} 5607 } 5608 5609 // Verify we have permission to write (no need to make a successor yet). 5610 md, err := fbo.getMDForWriteLockedForFilename(ctx, lState, "") 5611 if err != nil { 5612 return 5613 } 5614 5615 de, err := fbo.blocks.GetEntryEvenIfDeleted( 5616 ctx, lState, md.ReadOnly(), filePath) 5617 if err != nil { 5618 return err 5619 } 5620 5621 // If the file is a symlink, do nothing (to match ext4 5622 // behavior). 5623 if de.Type == data.Sym || de.Type == data.Dir { 5624 fbo.vlog.CLogf(ctx, libkb.VLog1, "Ignoring setex on type %s", de.Type) 5625 return nil 5626 } 5627 5628 switch { 5629 case ex && (de.Type == data.File): 5630 de.Type = data.Exec 5631 case !ex && (de.Type == data.Exec): 5632 de.Type = data.File 5633 default: 5634 // Treating this as a no-op, without updating the ctime, is a 5635 // POSIX violation, but it's an important optimization to keep 5636 // permissions-preserving rsyncs fast. 5637 fbo.vlog.CLogf(ctx, libkb.VLog1, "Ignoring no-op setex") 5638 return nil 5639 } 5640 5641 de.Ctime = fbo.nowUnixNano() 5642 5643 parentPtr := filePath.ParentPath().TailPointer() 5644 sao, err := newSetAttrOp( 5645 filePath.TailName().Plaintext(), parentPtr, exAttr, 5646 filePath.TailPointer()) 5647 if err != nil { 5648 return err 5649 } 5650 sao.AddSelfUpdate(parentPtr) 5651 5652 // If the node has been unlinked, we can safely ignore this setex. 5653 if fbo.nodeCache.IsUnlinked(file) { 5654 fbo.vlog.CLogf( 5655 ctx, libkb.VLog1, "Skipping setex for a removed file %v", 5656 filePath.TailPointer()) 5657 _ = fbo.blocks.UpdateCachedEntryAttributesOnRemovedFile( 5658 ctx, lState, md.ReadOnly(), sao, filePath, de) 5659 return nil 5660 } 5661 5662 sao.setFinalPath(filePath) 5663 5664 dirCacheUndoFn, err := fbo.blocks.SetAttrInDirEntryInCache( 5665 ctx, lState, md, filePath, de, sao.Attr) 5666 if err != nil { 5667 return err 5668 } 5669 return fbo.notifyAndSyncOrSignal( 5670 ctx, lState, dirCacheUndoFn, []Node{file}, sao, md.ReadOnly()) 5671 } 5672 5673 func (fbo *folderBranchOps) SetEx( 5674 ctx context.Context, file Node, ex bool) (err error) { 5675 startTime, timer := fbo.startOp( 5676 ctx, "SetEx %s %t", getNodeIDStr(file), ex) 5677 defer func() { 5678 fbo.endOp( 5679 ctx, startTime, timer, "SetEx %s %t done: %+v", 5680 getNodeIDStr(file), ex, err) 5681 }() 5682 5683 err = fbo.checkNodeForWrite(ctx, file) 5684 if err != nil { 5685 return 5686 } 5687 5688 return fbo.doMDWriteWithRetryUnlessCanceled(ctx, 5689 func(lState *kbfssync.LockState) error { 5690 return fbo.setExLocked(ctx, lState, file, ex) 5691 }) 5692 } 5693 5694 func (fbo *folderBranchOps) setMtimeLocked( 5695 ctx context.Context, lState *kbfssync.LockState, file Node, 5696 mtime *time.Time) error { 5697 fbo.mdWriterLock.AssertLocked(lState) 5698 5699 filePath, err := fbo.pathFromNodeForMDWriteLocked(lState, file) 5700 if err != nil { 5701 return err 5702 } 5703 5704 if !filePath.HasValidParent() { 5705 return InvalidParentPathError{filePath} 5706 } 5707 5708 // Verify we have permission to write (no need to make a successor yet). 5709 md, err := fbo.getMDForWriteLockedForFilename(ctx, lState, "") 5710 if err != nil { 5711 return err 5712 } 5713 5714 de, err := fbo.blocks.GetEntryEvenIfDeleted( 5715 ctx, lState, md.ReadOnly(), filePath) 5716 if err != nil { 5717 return err 5718 } 5719 de.Mtime = mtime.UnixNano() 5720 // setting the mtime counts as changing the file MD, so must set ctime too 5721 de.Ctime = fbo.nowUnixNano() 5722 5723 parentPtr := filePath.ParentPath().TailPointer() 5724 sao, err := newSetAttrOp( 5725 filePath.TailName().Plaintext(), parentPtr, mtimeAttr, 5726 filePath.TailPointer()) 5727 if err != nil { 5728 return err 5729 } 5730 sao.AddSelfUpdate(parentPtr) 5731 5732 // If the node has been unlinked, we can safely ignore this 5733 // setmtime. 5734 if fbo.nodeCache.IsUnlinked(file) { 5735 fbo.vlog.CLogf( 5736 ctx, libkb.VLog1, "Skipping setmtime for a removed file %v", 5737 filePath.TailPointer()) 5738 _ = fbo.blocks.UpdateCachedEntryAttributesOnRemovedFile( 5739 ctx, lState, md.ReadOnly(), sao, filePath, de) 5740 return nil 5741 } 5742 5743 sao.setFinalPath(filePath) 5744 5745 dirCacheUndoFn, err := fbo.blocks.SetAttrInDirEntryInCache( 5746 ctx, lState, md.ReadOnly(), filePath, de, sao.Attr) 5747 if err != nil { 5748 return err 5749 } 5750 return fbo.notifyAndSyncOrSignal( 5751 ctx, lState, dirCacheUndoFn, []Node{file}, sao, md.ReadOnly()) 5752 } 5753 5754 func (fbo *folderBranchOps) SetMtime( 5755 ctx context.Context, file Node, mtime *time.Time) (err error) { 5756 startTime, timer := fbo.startOp( 5757 ctx, "SetMtime %s %v", getNodeIDStr(file), mtime) 5758 defer func() { 5759 fbo.endOp( 5760 ctx, startTime, timer, "SetMtime %s %v done: %+v", 5761 getNodeIDStr(file), mtime, err) 5762 }() 5763 5764 if mtime == nil { 5765 // Can happen on some OSes (e.g. OSX) when trying to set the atime only 5766 return nil 5767 } 5768 5769 err = fbo.checkNodeForWrite(ctx, file) 5770 if err != nil { 5771 return 5772 } 5773 5774 return fbo.doMDWriteWithRetryUnlessCanceled(ctx, 5775 func(lState *kbfssync.LockState) error { 5776 return fbo.setMtimeLocked(ctx, lState, file, mtime) 5777 }) 5778 } 5779 5780 type cleanupFn func(context.Context, *kbfssync.LockState, []data.BlockPointer, error) 5781 5782 // startSyncLocked readies the blocks and other state needed to sync a 5783 // single file. It returns: 5784 // 5785 // - `doSync`: Whether or not the sync should actually happen. 5786 // - `stillDirty`: Whether the file should still be considered dirty when 5787 // this function returns. (That is, if `doSync` is false, and `stillDirty` 5788 // is true, then the file has outstanding changes but the sync was vetoed for 5789 // some other reason.) 5790 // - `fblock`: the root file block for the file being sync'd. 5791 // - `lbc`: A local block cache consisting of a dirtied version of the parent 5792 // directory for this file. 5793 // - `bps`: All the blocks that need to be put to the server. 5794 // - `syncState`: Must be passed to the `FinishSyncLocked` call after the 5795 // update completes. 5796 // - `cleanupFn`: A function that, if non-nil, must be called after the sync 5797 // is done. `cleanupFn` should be passed the set of bad blocks that couldn't 5798 // be sync'd (if any), and the error. 5799 // - `err`: The best, greatest return value, everyone says it's absolutely 5800 // stunning. 5801 func (fbo *folderBranchOps) startSyncLocked(ctx context.Context, 5802 lState *kbfssync.LockState, md *RootMetadata, node Node, file data.Path) ( 5803 doSync, stillDirty bool, fblock *data.FileBlock, dirtyDe *data.DirEntry, 5804 bps blockPutStateCopiable, syncState fileSyncState, 5805 cleanup cleanupFn, err error) { 5806 fbo.mdWriterLock.AssertLocked(lState) 5807 5808 // if the cache for this file isn't dirty, we're done 5809 if !fbo.blocks.IsDirty(lState, file) { 5810 return false, false, nil, nil, nil, fileSyncState{}, nil, nil 5811 } 5812 5813 // If the MD doesn't match the MD expected by the path, that 5814 // implies we are using a cached path, which implies the node has 5815 // been unlinked. In that case, we can safely ignore this sync. 5816 if fbo.nodeCache.IsUnlinked(node) { 5817 fbo.vlog.CLogf( 5818 ctx, libkb.VLog1, "Skipping sync for a removed file %v", 5819 file.TailPointer()) 5820 // Removing the cached info here is a little sketchy, 5821 // since there's no guarantee that this sync comes 5822 // from closing the file, and we still want to serve 5823 // stat calls accurately if the user still has an open 5824 // handle to this file. 5825 // 5826 // Note in particular that if a file just had a dirty 5827 // directory entry cached (due to an attribute change on a 5828 // removed file, for example), this will clear that attribute 5829 // change. If there's still an open file handle, the user 5830 // won't be able to see the change anymore. 5831 // 5832 // TODO: Hook this in with the node cache GC logic to be 5833 // perfectly accurate (but at the same time, we'd then have to 5834 // fix up the intentional panic in the background flusher to 5835 // be more tolerant of long-lived dirty, removed files). 5836 err := fbo.blocks.ClearCacheInfo(lState, file) 5837 if err != nil { 5838 return false, false, nil, nil, nil, fileSyncState{}, nil, err 5839 } 5840 fbo.status.rmDirtyNode(node) 5841 return false, true, nil, nil, nil, fileSyncState{}, nil, nil 5842 } 5843 5844 if file.IsValidForNotification() { 5845 // notify the daemon that a write is being performed 5846 fbo.config.Reporter().Notify(ctx, writeNotification(file, false)) 5847 defer fbo.config.Reporter().Notify(ctx, writeNotification(file, true)) 5848 } 5849 5850 fblock, bps, dirtyDe, syncState, err = 5851 fbo.blocks.StartSync(ctx, lState, md, file) 5852 cleanup = func(ctx context.Context, lState *kbfssync.LockState, 5853 blocksToRemove []data.BlockPointer, err error) { 5854 fbo.blocks.CleanupSyncState( 5855 ctx, lState, md.ReadOnly(), file, blocksToRemove, syncState, err) 5856 } 5857 if err != nil { 5858 return false, true, nil, nil, nil, fileSyncState{}, cleanup, err 5859 } 5860 5861 return true, true, fblock, dirtyDe, bps, syncState, cleanup, nil 5862 } 5863 5864 func addSelfUpdatesAndParent( 5865 p data.Path, op op, parentsToAddChainsFor map[data.BlockPointer]bool) { 5866 for i, pn := range p.Path { 5867 if i == len(p.Path)-1 { 5868 op.AddSelfUpdate(pn.BlockPointer) 5869 } else { 5870 parentsToAddChainsFor[pn.BlockPointer] = true 5871 } 5872 } 5873 } 5874 5875 func (fbo *folderBranchOps) syncAllLocked( 5876 ctx context.Context, lState *kbfssync.LockState, excl Excl) (err error) { 5877 fbo.mdWriterLock.AssertLocked(lState) 5878 5879 dirtyDirs, holdNewWritesCh := fbo.blocks.GetDirtyDirBlockRefs(lState) 5880 doCloseHoldNewWritesCh := true 5881 defer func() { 5882 if doCloseHoldNewWritesCh { 5883 close(holdNewWritesCh) 5884 } 5885 }() 5886 defer fbo.blocks.GetDirtyDirBlockRefsDone(lState) 5887 5888 dirtyFiles := fbo.blocks.GetDirtyFileBlockRefs(lState) 5889 5890 if len(dirtyFiles) == 0 && len(dirtyDirs) == 0 { 5891 return nil 5892 } 5893 5894 startTime, timer := fbo.startOp(ctx, "syncAllLocked") 5895 defer func() { 5896 fbo.endOp(ctx, startTime, timer, 5897 "syncAllLocked (%d files, %d dirs) done: %+v", 5898 len(dirtyFiles), len(dirtyDirs), err) 5899 }() 5900 5901 ctx = fbo.config.MaybeStartTrace(ctx, "FBO.SyncAll", 5902 fmt.Sprintf("%d files, %d dirs", len(dirtyFiles), len(dirtyDirs))) 5903 defer func() { fbo.config.MaybeFinishTrace(ctx, err) }() 5904 5905 // Verify we have permission to write. We do this after the dirty 5906 // check because otherwise readers who call syncAll would get an 5907 // error. 5908 md, err := fbo.getSuccessorMDForWriteLocked(ctx, lState) 5909 if err != nil { 5910 return err 5911 } 5912 5913 bps := newBlockPutStateMemory(0) 5914 resolvedPaths := make(map[data.BlockPointer]data.Path) 5915 dbm := newDirBlockMapMemory() 5916 5917 var cleanups []func(context.Context, *kbfssync.LockState, error) 5918 defer func() { 5919 for _, cf := range cleanups { 5920 cf(ctx, lState, err) 5921 } 5922 }() 5923 5924 fbo.log.LazyTrace(ctx, "Syncing %d dir(s)", len(dirtyDirs)) 5925 5926 // Get the most up-to-date mtime and ctime in the root block. 5927 rootDe, err := fbo.blocks.GetEntry(ctx, lState, md.ReadOnly(), data.Path{}) 5928 if err != nil { 5929 return err 5930 } 5931 5932 // First prep all the directories. 5933 fbo.vlog.CLogf(ctx, libkb.VLog1, "Syncing %d dir(s)", len(dirtyDirs)) 5934 for _, ref := range dirtyDirs { 5935 node := fbo.nodeCache.Get(ref) 5936 if node == nil { 5937 continue 5938 } 5939 5940 dir := fbo.nodeCache.PathFromNode(node) 5941 dblock, err := fbo.blocks.GetDirtyDirCopy( 5942 ctx, lState, md, dir, data.BlockWrite) 5943 if err != nil { 5944 return err 5945 } 5946 5947 err = dbm.putBlock(ctx, dir.TailPointer(), dblock) 5948 if err != nil { 5949 return err 5950 } 5951 if !fbo.nodeCache.IsUnlinked(node) { 5952 resolvedPaths[dir.TailPointer()] = dir 5953 } 5954 5955 // Add the parent directory of this dirty directory to the 5956 // `dbm`, to reflect the updated mtime/ctimes of the dirty 5957 // directory. 5958 if dir.HasValidParent() { 5959 parentPath := dir.ParentPath() 5960 hasBlock, err := dbm.hasBlock(ctx, parentPath.TailPointer()) 5961 if err != nil { 5962 return err 5963 } 5964 if !hasBlock { 5965 parentBlock, err := fbo.blocks.GetDirtyDirCopy( 5966 ctx, lState, md, *parentPath, data.BlockWrite) 5967 if err != nil { 5968 return err 5969 } 5970 err = dbm.putBlock(ctx, parentPath.TailPointer(), parentBlock) 5971 if err != nil { 5972 return err 5973 } 5974 } 5975 } 5976 5977 // On a successful sync, clean up the cached entries and the 5978 // dirty blocks. TODO: avoid closures by saving `dir` and 5979 // `node` in a list for deferred processing. 5980 cleanups = append(cleanups, 5981 func(ctx context.Context, lState *kbfssync.LockState, err error) { 5982 if err != nil { 5983 return 5984 } 5985 fbo.status.rmDirtyNode(node) 5986 }) 5987 } 5988 defer func() { 5989 // If the sync is successful, we can clear out all buffered 5990 // directory operations. 5991 if err == nil { 5992 fbo.dirOps = nil 5993 } 5994 }() 5995 5996 fbo.log.LazyTrace(ctx, "Processing %d op(s)", len(fbo.dirOps)) 5997 5998 newBlocks := make(map[data.BlockPointer]bool) 5999 fileBlocks := newFileBlockMapMemory() 6000 parentsToAddChainsFor := make(map[data.BlockPointer]bool) 6001 for _, dop := range fbo.dirOps { 6002 // Copy the op before modifying it, in case there's an error 6003 // and we have to retry with the original ops. 6004 newOp := dop.dirOp.deepCopy() 6005 md.AddOp(newOp) 6006 6007 // Add "updates" for all the op updates, and make chains for 6008 // the rest of the parent directories, so they're treated like 6009 // updates during the prepping. 6010 for _, n := range dop.nodes { 6011 p := fbo.nodeCache.PathFromNode(n) 6012 if _, ok := newOp.(*setAttrOp); ok { 6013 // For a setattr, the node is the file, but that 6014 // doesn't get updated, so use the current parent 6015 // node. 6016 p = *p.ParentPath() 6017 } 6018 6019 addSelfUpdatesAndParent(p, newOp, parentsToAddChainsFor) 6020 } 6021 6022 var ref data.BlockRef 6023 switch realOp := newOp.(type) { 6024 case *createOp: 6025 if realOp.Type == data.Sym { 6026 continue 6027 } 6028 6029 // New files and directories explicitly need 6030 // pointer-updating, because the sync process will turn 6031 // them into simple refs and will forget about the local, 6032 // temporary ID. 6033 newNode := dop.nodes[1] 6034 newPath := fbo.nodeCache.PathFromNode(newNode) 6035 newPointer := newPath.TailPointer() 6036 newBlocks[newPointer] = true 6037 6038 if realOp.Type != data.Dir { 6039 continue 6040 } 6041 6042 hasBlock, err := dbm.hasBlock(ctx, newPointer) 6043 if err != nil { 6044 return err 6045 } 6046 var dblock *data.DirBlock 6047 if hasBlock { 6048 dblock, err = dbm.getBlock(ctx, newPointer) 6049 if err != nil { 6050 return err 6051 } 6052 } else { 6053 // New directories that aren't otherwise dirty need to 6054 // be added to both the `dbm` and `resolvedPaths` so 6055 // they are properly synced, and removed from the 6056 // dirty block list. 6057 dblock, err = fbo.blocks.GetDirtyDirCopy( 6058 ctx, lState, md, newPath, data.BlockWrite) 6059 if err != nil { 6060 return err 6061 } 6062 err = dbm.putBlock(ctx, newPointer, dblock) 6063 if err != nil { 6064 return err 6065 } 6066 if !fbo.nodeCache.IsUnlinked(newNode) { 6067 resolvedPaths[newPointer] = newPath 6068 } 6069 // TODO: avoid closures by saving `newPath` and 6070 // `newNode` in a list for deferred processing. 6071 cleanups = append(cleanups, 6072 func(ctx context.Context, lState *kbfssync.LockState, 6073 err error) { 6074 if err != nil { 6075 return 6076 } 6077 _ = fbo.status.rmDirtyNode(newNode) 6078 _ = fbo.config.DirtyBlockCache().Delete( 6079 fbo.id(), newPointer, fbo.branch()) 6080 }) 6081 } 6082 6083 if len(dblock.Children) > 0 || len(dblock.IPtrs) > 0 { 6084 continue 6085 } 6086 6087 // If the directory is empty, we need to explicitly clean 6088 // up its entry after syncing. 6089 ref = newPath.TailRef() 6090 case *renameOp: 6091 ref = realOp.Renamed.Ref() 6092 case *setAttrOp: 6093 ref = realOp.File.Ref() 6094 default: 6095 continue 6096 } 6097 6098 // For create, rename and setattr ops, the target will have a 6099 // dirty entry, but may not have any outstanding operations on 6100 // it, so it needs to be cleaned up manually. 6101 defer func() { 6102 if err != nil { 6103 return 6104 } 6105 node := fbo.nodeCache.Get(ref) 6106 if node != nil { 6107 fbo.status.rmDirtyNode(node) 6108 } 6109 }() 6110 } 6111 6112 var blocksToRemove []data.BlockPointer 6113 updatePointerFn := func() error { 6114 // Any new files or directories need their pointers explicitly 6115 // updated, because the sync will be treating them as a new 6116 // ref, and not an update. 6117 for ptr, bs := range bps.blockStates { 6118 if newBlocks[bs.oldPtr] { 6119 fbo.blocks.updatePointer(md.ReadOnly(), bs.oldPtr, ptr, false) 6120 } 6121 } 6122 return nil 6123 } 6124 6125 fbo.log.LazyTrace(ctx, "Syncing %d file(s)", len(dirtyFiles)) 6126 6127 // TODO: find a way to avoid so many dynamic closure dispatches. 6128 var afterUpdateFns []func() error 6129 6130 fbo.vlog.CLogf(ctx, libkb.VLog1, "Syncing %d file(s)", len(dirtyFiles)) 6131 fileSyncBlocks := newBlockPutStateMemory(1) 6132 for _, ref := range dirtyFiles { 6133 node := fbo.nodeCache.Get(ref) 6134 if node == nil { 6135 continue 6136 } 6137 file := fbo.nodeCache.PathFromNode(node) 6138 fbo.vlog.CLogf(ctx, libkb.VLog1, "Syncing file %v (%s)", ref, file) 6139 6140 // Start the sync for this dirty file. 6141 doSync, stillDirty, fblock, dirtyDe, newBps, syncState, cleanup, err := 6142 fbo.startSyncLocked(ctx, lState, md, node, file) 6143 if cleanup != nil { 6144 // Note: This passes the same `blocksToRemove` into each 6145 // cleanup function. That's ok, as only the ones 6146 // pertaining to a particular syncing file will be acted 6147 // on. 6148 cleanups = append(cleanups, 6149 func(ctx context.Context, lState *kbfssync.LockState, 6150 err error) { 6151 cleanup(ctx, lState, blocksToRemove, err) 6152 }) 6153 } 6154 if err != nil { 6155 return err 6156 } 6157 if !doSync { 6158 if !stillDirty { 6159 fbo.status.rmDirtyNode(node) 6160 } 6161 continue 6162 } 6163 6164 // Merge the per-file sync info into the batch sync info. 6165 err = bps.mergeOtherBps(ctx, newBps) 6166 if err != nil { 6167 return err 6168 } 6169 err = fileSyncBlocks.mergeOtherBps(ctx, newBps) 6170 if err != nil { 6171 return err 6172 } 6173 resolvedPaths[file.TailPointer()] = file 6174 parent := file.ParentPath().TailPointer() 6175 err = fileBlocks.putTopBlock(ctx, parent, file.TailName(), fblock) 6176 if err != nil { 6177 return err 6178 } 6179 6180 // Collect its `afterUpdateFn` along with all the others, so 6181 // they all get invoked under the same lock, to avoid any 6182 // weird races. 6183 afterUpdateFns = append(afterUpdateFns, func() error { 6184 // This will be called after the node cache is updated, so 6185 // this newPath will be correct. 6186 newPath := fbo.nodeCache.PathFromNode(node) 6187 stillDirty, err := fbo.blocks.FinishSyncLocked( 6188 ctx, lState, file, newPath, md.ReadOnly(), syncState, fbo.fbm) 6189 if !stillDirty { 6190 fbo.status.rmDirtyNode(node) 6191 } 6192 return err 6193 }) 6194 6195 // Add an "update" for all the parent directory updates, and 6196 // make a chain for the file itself, so they're treated like 6197 // updates during the prepping. 6198 lastOp := md.Data().Changes.Ops[len(md.Data().Changes.Ops)-1] 6199 addSelfUpdatesAndParent(file, lastOp, parentsToAddChainsFor) 6200 6201 // Update the combined local block cache with this file's 6202 // dirty entry. 6203 if dirtyDe != nil { 6204 err := fbo.blocks.mergeDirtyEntryWithDBM( 6205 ctx, lState, file, md, dbm, *dirtyDe) 6206 if err != nil { 6207 return err 6208 } 6209 } 6210 } 6211 6212 // Now that we've copied all the directory blocks and marked dirty 6213 // files for syncing, we can unblock new writes. 6214 close(holdNewWritesCh) 6215 doCloseHoldNewWritesCh = false 6216 6217 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 6218 if err != nil { 6219 return err 6220 } 6221 6222 tempIRMD := ImmutableRootMetadata{ 6223 ReadOnlyRootMetadata: md.ReadOnly(), 6224 lastWriterVerifyingKey: session.VerifyingKey, 6225 } 6226 6227 fbo.log.LazyTrace(ctx, "Prepping update") 6228 6229 // Create a set of chains for this batch, a succinct summary of 6230 // the file and directory blocks that need to change during this 6231 // sync. 6232 syncChains, err := newCRChains( 6233 ctx, fbo.config.Codec(), fbo.config, []chainMetadata{tempIRMD}, 6234 &fbo.blocks, false) 6235 if err != nil { 6236 return err 6237 } 6238 for ptr := range parentsToAddChainsFor { 6239 syncChains.addNoopChain(ptr) 6240 } 6241 6242 // All originals never made it to the server, so don't unmerged 6243 // them. 6244 syncChains.doNotUnrefPointers = syncChains.createdOriginals 6245 head, _ := fbo.getHead(ctx, lState, mdNoCommit) 6246 dummyHeadChains := newCRChainsEmpty(fbo.makeObfuscator) 6247 dummyHeadChains.mostRecentChainMDInfo = head 6248 6249 // Squash the batch of updates together into a set of blocks and 6250 // ready `md` for putting to the server. 6251 md.AddOp(newResolutionOp()) 6252 _, blocksToDelete, err := fbo.prepper.prepUpdateForPaths( 6253 ctx, lState, md, syncChains, dummyHeadChains, tempIRMD, head, 6254 resolvedPaths, dbm, fileBlocks, fbo.config.DirtyBlockCache(), bps, 6255 prepFolderDontCopyIndirectFileBlocks) 6256 if err != nil { 6257 return err 6258 } 6259 if len(blocksToDelete) > 0 { 6260 return errors.Errorf("Unexpectedly found unflushed blocks to delete "+ 6261 "during syncAllLocked: %v", blocksToDelete) 6262 } 6263 6264 defer func() { 6265 if err != nil { 6266 // Remove any blocks that are covered by file syncs -- 6267 // those might get reused upon sync retry. All other 6268 // blocks are fair game for cleanup though. 6269 removeErr := bps.removeOtherBps(ctx, fileSyncBlocks) 6270 if removeErr != nil { 6271 fbo.log.CDebugf(ctx, "Error removing other bps: %+v", removeErr) 6272 } 6273 fbo.fbm.cleanUpBlockState(md.ReadOnly(), bps, blockDeleteOnMDFail) 6274 } 6275 }() 6276 6277 // Put all the blocks. 6278 cacheType := DiskBlockAnyCache 6279 if fbo.isSyncedTlf() { 6280 cacheType = DiskBlockSyncCache 6281 } 6282 blocksToRemove, err = doBlockPuts( 6283 ctx, fbo.config.BlockServer(), fbo.config.BlockCache(), 6284 fbo.config.Reporter(), fbo.log, fbo.deferLog, md.TlfID(), 6285 md.GetTlfHandle().GetCanonicalName(), bps, cacheType) 6286 if err != nil { 6287 return err 6288 } 6289 6290 // Call this under the same blockLock as when the pointers are 6291 // updated, so there's never any point in time where a read or 6292 // write might slip in after the pointers are updated, but before 6293 // the deferred writes are re-applied. 6294 afterUpdateFn := func() error { 6295 // Update pointers of new files first, before replaying any of 6296 // their deferred writes in `clearAllDirtyDirsLocked`. 6297 err := updatePointerFn() 6298 if err != nil { 6299 return err 6300 } 6301 6302 // Clear the dirty directories before the afterUpdateFns start 6303 // replaying deferred writes, so we don't lose the deferred 6304 // write state when we clear. 6305 fbo.blocks.clearAllDirtyDirsLocked(ctx, lState, md) 6306 var errs []error 6307 for _, auf := range afterUpdateFns { 6308 err := auf() 6309 if err != nil { 6310 errs = append(errs, err) 6311 } 6312 } 6313 if len(errs) == 1 { 6314 return errs[0] 6315 } else if len(errs) > 1 { 6316 return errors.Errorf("Got errors %+v", errs) 6317 } 6318 return nil 6319 } 6320 6321 // Set the root directory entry times to their updated values, 6322 // since the prepper doesn't do it for blocks that aren't in the 6323 // `dbm`. 6324 md.data.Dir.Mtime = rootDe.Mtime 6325 md.data.Dir.Ctime = rootDe.Ctime 6326 6327 return fbo.finalizeMDWriteLocked(ctx, lState, md, bps, excl, 6328 func(md ImmutableRootMetadata) error { 6329 // Just update the pointers using the resolutionOp, all 6330 // the ops have already been notified. 6331 affectedNodeIDs, err := fbo.blocks.UpdatePointers( 6332 md, lState, md.data.Changes.Ops[0], false, afterUpdateFn) 6333 if err != nil { 6334 return err 6335 } 6336 6337 fbo.observers.batchChanges(ctx, nil, affectedNodeIDs) 6338 return nil 6339 }) 6340 } 6341 6342 func (fbo *folderBranchOps) syncAllUnlocked( 6343 ctx context.Context, lState *kbfssync.LockState) error { 6344 fbo.mdWriterLock.Lock(lState) 6345 defer fbo.mdWriterLock.Unlock(lState) 6346 6347 select { 6348 case <-ctx.Done(): 6349 // We've already been canceled, possibly because we're a CR 6350 // and a write just called cr.ForceCancel. Don't allow the 6351 // SyncAll to complete, because if no other writes happen 6352 // we'll get stuck forever (see KBFS-2505). Instead, wait for 6353 // the next `SyncAll` to trigger. 6354 return ctx.Err() 6355 default: 6356 } 6357 6358 return fbo.syncAllLocked(ctx, lState, NoExcl) 6359 } 6360 6361 // SyncAll implements the KBFSOps interface for folderBranchOps. 6362 func (fbo *folderBranchOps) SyncAll( 6363 ctx context.Context, folderBranch data.FolderBranch) (err error) { 6364 startTime, timer := fbo.startOp(ctx, "SyncAll") 6365 defer func() { 6366 fbo.endOp(ctx, startTime, timer, "SyncAll done: %+v", err) 6367 }() 6368 6369 if folderBranch != fbo.folderBranch { 6370 return WrongOpsError{fbo.folderBranch, folderBranch} 6371 } 6372 6373 return fbo.doMDWriteWithRetryUnlessCanceled(ctx, 6374 func(lState *kbfssync.LockState) error { 6375 return fbo.syncAllLocked(ctx, lState, NoExcl) 6376 }) 6377 } 6378 6379 func (fbo *folderBranchOps) FolderStatus( 6380 ctx context.Context, folderBranch data.FolderBranch) ( 6381 fbs FolderBranchStatus, updateChan <-chan StatusUpdate, err error) { 6382 startTime, timer := fbo.startOp(ctx, "Status") 6383 defer func() { 6384 fbo.endOp(ctx, startTime, timer, "Status done: %+v", err) 6385 }() 6386 6387 if folderBranch != fbo.folderBranch { 6388 return FolderBranchStatus{}, nil, 6389 WrongOpsError{fbo.folderBranch, folderBranch} 6390 } 6391 6392 return fbo.status.getStatus(ctx, &fbo.blocks) 6393 } 6394 6395 func (fbo *folderBranchOps) FolderConflictStatus(ctx context.Context) ( 6396 keybase1.FolderConflictType, error) { 6397 isStuck, err := fbo.cr.isStuck() 6398 if err != nil { 6399 return keybase1.FolderConflictType_NONE, err 6400 } 6401 6402 if isStuck { 6403 return keybase1.FolderConflictType_IN_CONFLICT_AND_STUCK, nil 6404 } 6405 6406 lState := makeFBOLockState() 6407 if fbo.isUnmerged(lState) { 6408 return keybase1.FolderConflictType_IN_CONFLICT, nil 6409 } 6410 return keybase1.FolderConflictType_NONE, nil 6411 } 6412 6413 func (fbo *folderBranchOps) Status( 6414 ctx context.Context) ( 6415 fbs KBFSStatus, updateChan <-chan StatusUpdate, err error) { 6416 return KBFSStatus{}, nil, InvalidOpError{} 6417 } 6418 6419 // RegisterForChanges registers a single Observer to receive 6420 // notifications about this folder/branch. 6421 func (fbo *folderBranchOps) RegisterForChanges(obs Observer) error { 6422 // It's the caller's responsibility to make sure 6423 // RegisterForChanges isn't called twice for the same Observer 6424 fbo.observers.add(obs) 6425 return nil 6426 } 6427 6428 // UnregisterFromChanges stops an Observer from getting notifications 6429 // about the folder/branch. 6430 func (fbo *folderBranchOps) UnregisterFromChanges(obs Observer) error { 6431 fbo.observers.remove(obs) 6432 return nil 6433 } 6434 6435 // notifyBatchLocked sends out a notification for all the ops in md. 6436 func (fbo *folderBranchOps) notifyBatchLocked( 6437 ctx context.Context, lState *kbfssync.LockState, 6438 md ImmutableRootMetadata) error { 6439 fbo.headLock.AssertLocked(lState) 6440 6441 for _, op := range md.data.Changes.Ops { 6442 err := fbo.notifyOneOpLocked(ctx, lState, op, md.ReadOnly(), false) 6443 if err != nil { 6444 return err 6445 } 6446 } 6447 return nil 6448 } 6449 6450 // searchForNode tries to figure out the path to the given 6451 // blockPointer, using only the block updates that happened as part of 6452 // a given MD update operation. 6453 func (fbo *folderBranchOps) searchForNode(ctx context.Context, 6454 ptr data.BlockPointer, md ReadOnlyRootMetadata) (Node, error) { 6455 // Record which pointers are new to this update, and thus worth 6456 // searching. 6457 newPtrs := make(map[data.BlockPointer]bool) 6458 for _, op := range md.data.Changes.Ops { 6459 for _, update := range op.allUpdates() { 6460 newPtrs[update.Ref] = true 6461 } 6462 for _, ref := range op.Refs() { 6463 newPtrs[ref] = true 6464 } 6465 } 6466 6467 nodeMap, _, err := fbo.blocks.SearchForNodes(ctx, fbo.nodeCache, 6468 []data.BlockPointer{ptr}, newPtrs, md, md.data.Dir.BlockPointer) 6469 if err != nil { 6470 return nil, err 6471 } 6472 6473 n, ok := nodeMap[ptr] 6474 if !ok { 6475 return nil, NodeNotFoundError{ptr} 6476 } 6477 6478 return n, nil 6479 } 6480 6481 func (fbo *folderBranchOps) getUnlinkPathBeforeUpdatingPointers( 6482 ctx context.Context, lState *kbfssync.LockState, md ReadOnlyRootMetadata, 6483 op op) (unlinkPath data.Path, unlinkDe data.DirEntry, toUnlink bool, err error) { 6484 fbo.mdWriterLock.AssertLocked(lState) 6485 if len(md.data.Changes.Ops) == 0 { 6486 return data.Path{}, data.DirEntry{}, false, errors.New("md needs at least one op") 6487 } 6488 6489 var node Node 6490 var childName data.PathPartString 6491 6492 requireResFix := false 6493 switch realOp := op.(type) { 6494 case *rmOp: 6495 if realOp.Dir.Ref == realOp.Dir.Unref { 6496 requireResFix = true 6497 } 6498 node = fbo.nodeCache.Get(realOp.Dir.Unref.Ref()) 6499 childName = realOp.obfuscatedOldName() 6500 case *renameOp: 6501 if realOp.NewDir.Unref != data.ZeroPtr { 6502 // moving to a new dir 6503 if realOp.NewDir.Ref == realOp.NewDir.Unref { 6504 requireResFix = true 6505 } 6506 node = fbo.nodeCache.Get(realOp.NewDir.Unref.Ref()) 6507 } else { 6508 // moving to the same dir 6509 if realOp.OldDir.Ref == realOp.OldDir.Unref { 6510 requireResFix = true 6511 } 6512 node = fbo.nodeCache.Get(realOp.OldDir.Unref.Ref()) 6513 } 6514 childName = realOp.obfuscatedNewName() 6515 } 6516 if node == nil { 6517 return data.Path{}, data.DirEntry{}, false, nil 6518 } 6519 6520 p, err := fbo.pathFromNodeForRead(node) 6521 if err != nil { 6522 return data.Path{}, data.DirEntry{}, false, err 6523 } 6524 6525 // If the first op in this MD update is a resolutionOp, we need to 6526 // inspect it to look for the *real* original pointer for this 6527 // node. Though only do that if the op we're processing is 6528 // actually a part of this MD object; if it's the latest cached 6529 // dirOp, then the resOp we're looking at belongs to a previous 6530 // revision. 6531 if resOp, ok := md.data.Changes.Ops[0].(*resolutionOp); ok && 6532 (len(fbo.dirOps) == 0 || op != fbo.dirOps[len(fbo.dirOps)-1].dirOp) { 6533 for _, update := range resOp.allUpdates() { 6534 if update.Ref == p.TailPointer() { 6535 fbo.vlog.CLogf( 6536 ctx, libkb.VLog1, 6537 "Backing up ptr %v in op %s to original pointer %v", 6538 p.TailPointer(), op, update.Unref) 6539 p.Path[len(p.Path)-1].BlockPointer = update.Unref 6540 requireResFix = false 6541 break 6542 } 6543 } 6544 } 6545 6546 if requireResFix { 6547 // If we didn't fix up the pointer using a resolutionOp, the 6548 // directory was likely created during this md update, and so 6549 // no unlinking is needed. 6550 fbo.vlog.CLogf( 6551 ctx, libkb.VLog1, 6552 "Ignoring unlink when resolutionOp never fixed up %v", 6553 p.TailPointer()) 6554 return data.Path{}, data.DirEntry{}, false, nil 6555 } 6556 6557 // If the original (clean) parent block is already GC'd from the 6558 // server, this might not work, but hopefully we'd be 6559 // fast-forwarding in that case anyway. 6560 ob := fbo.makeObfuscator() 6561 childPath := p.ChildPathNoPtr(childName, ob) 6562 de, err := fbo.blocks.GetEntry(ctx, lState, md, childPath) 6563 if err != nil { 6564 fbo.log.CDebugf(ctx, "Couldn't get the dir entry for %s in %v: %+v", 6565 childName, p.TailPointer(), err) 6566 return data.Path{}, data.DirEntry{}, false, nil 6567 } 6568 childPath = p.ChildPath(childName, de.BlockPointer, ob) 6569 return childPath, de, true, nil 6570 } 6571 6572 func (fbo *folderBranchOps) notifyOneOpLocked(ctx context.Context, 6573 lState *kbfssync.LockState, op op, md ReadOnlyRootMetadata, 6574 shouldPrefetch bool) error { 6575 fbo.mdWriterLock.AssertLocked(lState) 6576 fbo.headLock.AssertLocked(lState) 6577 6578 if !fbo.config.Mode().NodeCacheEnabled() { 6579 // There is no node cache in minimal mode, so there's nothing 6580 // to update. 6581 return nil 6582 } 6583 6584 // We need to get unlinkPath before calling UpdatePointers so that 6585 // nodeCache.Unlink can properly update cachedPath. 6586 unlinkPath, unlinkDe, toUnlink, err := 6587 fbo.getUnlinkPathBeforeUpdatingPointers(ctx, lState, md, op) 6588 if err != nil { 6589 return err 6590 } 6591 6592 affectedNodeIDs, err := fbo.blocks.UpdatePointers( 6593 md, lState, op, shouldPrefetch, nil) 6594 if err != nil { 6595 return err 6596 } 6597 6598 // Cancel any block prefetches for unreferenced blocks. 6599 for _, ptr := range op.Unrefs() { 6600 fbo.config.BlockOps().Prefetcher().CancelPrefetch(ptr) 6601 } 6602 6603 var changes []NodeChange 6604 switch realOp := op.(type) { 6605 default: 6606 fbo.log.CDebugf(ctx, "Unknown op: %s", op) 6607 case *createOp: 6608 node := fbo.nodeCache.Get(realOp.Dir.Ref.Ref()) 6609 if node == nil { 6610 break 6611 } 6612 fbo.vlog.CLogf(ctx, libkb.VLog1, "notifyOneOp: create %s in node %s", 6613 realOp.NewName, getNodeIDStr(node)) 6614 changes = append(changes, NodeChange{ 6615 Node: node, 6616 DirUpdated: []data.PathPartString{realOp.obfuscatedNewName()}, 6617 }) 6618 case *rmOp: 6619 node := fbo.nodeCache.Get(realOp.Dir.Ref.Ref()) 6620 if node == nil { 6621 break 6622 } 6623 fbo.vlog.CLogf(ctx, libkb.VLog1, "notifyOneOp: remove %s in node %s", 6624 realOp.OldName, getNodeIDStr(node)) 6625 changes = append(changes, NodeChange{ 6626 Node: node, 6627 DirUpdated: []data.PathPartString{realOp.obfuscatedOldName()}, 6628 }) 6629 6630 // If this node exists, then the child node might exist too, 6631 // and we need to unlink it in the node cache. 6632 if toUnlink { 6633 _ = fbo.nodeCache.Unlink(unlinkDe.Ref(), unlinkPath, unlinkDe) 6634 } 6635 case *renameOp: 6636 oldNode := fbo.nodeCache.Get(realOp.OldDir.Ref.Ref()) 6637 if oldNode != nil { 6638 changes = append(changes, NodeChange{ 6639 Node: oldNode, 6640 DirUpdated: []data.PathPartString{realOp.obfuscatedOldName()}, 6641 }) 6642 } 6643 var newNode Node 6644 if realOp.NewDir.Ref != data.ZeroPtr { 6645 newNode = fbo.nodeCache.Get(realOp.NewDir.Ref.Ref()) 6646 if newNode != nil { 6647 changes = append(changes, NodeChange{ 6648 Node: newNode, 6649 DirUpdated: []data.PathPartString{ 6650 realOp.obfuscatedNewName()}, 6651 }) 6652 } 6653 } else { 6654 newNode = oldNode 6655 if oldNode != nil { 6656 // Add another name to the existing NodeChange. 6657 changes[len(changes)-1].DirUpdated = 6658 append(changes[len(changes)-1].DirUpdated, 6659 realOp.obfuscatedNewName()) 6660 } 6661 } 6662 6663 if oldNode != nil { 6664 fbo.vlog.CLogf( 6665 ctx, libkb.VLog1, "notifyOneOp: rename %v from %s/%s to %s/%s", 6666 realOp.Renamed, realOp.OldName, getNodeIDStr(oldNode), 6667 realOp.NewName, getNodeIDStr(newNode)) 6668 6669 if newNode == nil { 6670 if childNode := 6671 fbo.nodeCache.Get(realOp.Renamed.Ref()); childNode != nil { 6672 // if the childNode exists, we still have to update 6673 // its path to go through the new node. That means 6674 // creating nodes for all the intervening paths. 6675 // Unfortunately we don't have enough information to 6676 // know what the newPath is; we have to guess it from 6677 // the updates. 6678 var err error 6679 newNode, err = 6680 fbo.searchForNode(ctx, realOp.NewDir.Ref, md) 6681 if newNode == nil { 6682 fbo.log.CErrorf(ctx, "Couldn't find the new node: %v", 6683 err) 6684 } 6685 } 6686 } 6687 6688 if newNode != nil { 6689 if toUnlink { 6690 _ = fbo.nodeCache.Unlink( 6691 unlinkDe.Ref(), unlinkPath, unlinkDe) 6692 } 6693 _, err := fbo.nodeCache.Move( 6694 realOp.Renamed.Ref(), newNode, realOp.obfuscatedNewName()) 6695 if err != nil { 6696 return err 6697 } 6698 } 6699 } 6700 case *syncOp: 6701 node := fbo.nodeCache.Get(realOp.File.Ref.Ref()) 6702 if node == nil { 6703 break 6704 } 6705 fbo.vlog.CLogf( 6706 ctx, libkb.VLog1, "notifyOneOp: sync %d writes in node %s", 6707 len(realOp.Writes), getNodeIDStr(node)) 6708 6709 changes = append(changes, NodeChange{ 6710 Node: node, 6711 FileUpdated: realOp.Writes, 6712 }) 6713 case *setAttrOp: 6714 node := fbo.nodeCache.Get(realOp.Dir.Ref.Ref()) 6715 if node == nil { 6716 break 6717 } 6718 fbo.vlog.CLogf( 6719 ctx, libkb.VLog1, "notifyOneOp: setAttr %s for file %s in node %s", 6720 realOp.Attr, realOp.Name, getNodeIDStr(node)) 6721 6722 childNode := fbo.nodeCache.Get(realOp.File.Ref()) 6723 if childNode == nil { 6724 break 6725 } 6726 6727 changes = append(changes, NodeChange{ 6728 Node: childNode, 6729 }) 6730 case *GCOp: 6731 // Unreferenced blocks in a GCOp mean that we shouldn't cache 6732 // them anymore 6733 fbo.vlog.CLogf( 6734 ctx, libkb.VLog1, 6735 "notifyOneOp: GCOp with latest rev %d and %d unref'd blocks", 6736 realOp.LatestRev, len(realOp.Unrefs())) 6737 bcache := fbo.config.BlockCache() 6738 for _, ptr := range realOp.Unrefs() { 6739 if err := bcache.DeleteTransient(ptr.ID, fbo.id()); err != nil { 6740 fbo.log.CDebugf(ctx, 6741 "Couldn't delete transient entry for %v: %v", ptr, err) 6742 } 6743 } 6744 case *resolutionOp: 6745 // If there are any unrefs of blocks that have a node, this is an 6746 // implied rmOp (see KBFS-1424). 6747 reverseUpdates := make(map[data.BlockPointer]data.BlockPointer) 6748 for _, unref := range op.Unrefs() { 6749 node := fbo.nodeCache.Get(unref.Ref()) 6750 if node == nil { 6751 // TODO: even if we don't have the node that was 6752 // unreferenced, we might have its parent, and that 6753 // parent might need an invalidation. 6754 continue 6755 } 6756 6757 // If there is a node, unlink and invalidate. 6758 p, err := fbo.pathFromNodeForRead(node) 6759 if err != nil { 6760 fbo.log.CErrorf(ctx, "Couldn't get path: %v", err) 6761 continue 6762 } 6763 if !p.HasValidParent() { 6764 fbo.log.CErrorf(ctx, "Removed node %s has no parent", p) 6765 continue 6766 } 6767 parentPath := p.ParentPath() 6768 parentNode := fbo.nodeCache.Get(parentPath.TailRef()) 6769 if parentNode != nil { 6770 changes = append(changes, NodeChange{ 6771 Node: parentNode, 6772 DirUpdated: []data.PathPartString{p.TailName()}, 6773 }) 6774 } 6775 6776 fbo.vlog.CLogf( 6777 ctx, libkb.VLog1, "resolutionOp: remove %s, node %s", 6778 p.TailPointer(), getNodeIDStr(node)) 6779 // Revert the path back to the original BlockPointers, 6780 // before the updates were applied. 6781 if len(reverseUpdates) == 0 { 6782 for _, update := range op.allUpdates() { 6783 reverseUpdates[update.Ref] = update.Unref 6784 } 6785 } 6786 for i, pNode := range p.Path { 6787 if oldPtr, ok := reverseUpdates[pNode.BlockPointer]; ok { 6788 p.Path[i].BlockPointer = oldPtr 6789 } 6790 } 6791 de, err := fbo.blocks.GetEntry(ctx, lState, md.ReadOnly(), p) 6792 if err != nil { 6793 fbo.log.CDebugf(ctx, 6794 "Couldn't get the dir entry for %s/%v: %+v", 6795 p, p.TailPointer(), err) 6796 } 6797 _ = fbo.nodeCache.Unlink(p.TailRef(), p, de) 6798 } 6799 } 6800 6801 if len(changes) > 0 || len(affectedNodeIDs) > 0 { 6802 fbo.observers.batchChanges(ctx, changes, affectedNodeIDs) 6803 } 6804 return nil 6805 } 6806 6807 func (fbo *folderBranchOps) notifyOneOp(ctx context.Context, 6808 lState *kbfssync.LockState, op op, md ReadOnlyRootMetadata, 6809 shouldPrefetch bool) error { 6810 fbo.headLock.Lock(lState) 6811 defer fbo.headLock.Unlock(lState) 6812 return fbo.notifyOneOpLocked(ctx, lState, op, md, shouldPrefetch) 6813 } 6814 6815 func (fbo *folderBranchOps) getCurrMDRevisionLocked( 6816 lState *kbfssync.LockState) kbfsmd.Revision { 6817 fbo.headLock.AssertAnyLocked(lState) 6818 6819 if fbo.head != (ImmutableRootMetadata{}) { 6820 return fbo.head.Revision() 6821 } 6822 return kbfsmd.RevisionUninitialized 6823 } 6824 6825 func (fbo *folderBranchOps) getCurrMDRevision( 6826 lState *kbfssync.LockState) kbfsmd.Revision { 6827 fbo.headLock.RLock(lState) 6828 defer fbo.headLock.RUnlock(lState) 6829 return fbo.getCurrMDRevisionLocked(lState) 6830 } 6831 6832 type applyMDUpdatesFunc func( 6833 context.Context, *kbfssync.LockState, []ImmutableRootMetadata) error 6834 6835 func (fbo *folderBranchOps) applyMDUpdatesLocked(ctx context.Context, 6836 lState *kbfssync.LockState, rmds []ImmutableRootMetadata) (err error) { 6837 fbo.mdWriterLock.AssertLocked(lState) 6838 6839 if len(rmds) == 0 { 6840 return nil 6841 } 6842 latestMerged := rmds[len(rmds)-1] 6843 6844 // If there's anything in the journal, don't apply these MDs. 6845 // Wait for CR to happen. 6846 if !fbo.isUnmergedLocked(lState) { 6847 mergedRev, journalEnd, err := fbo.getJournalRevisions(ctx) 6848 if err == errNoFlushedRevisions { 6849 // If the journal is still on the initial revision, ignore 6850 // the error and fall through to ignore CR. 6851 mergedRev = kbfsmd.RevisionInitial 6852 } else if err != nil { 6853 return err 6854 } 6855 if mergedRev != kbfsmd.RevisionUninitialized { 6856 if latestMerged.Revision() > journalEnd { 6857 // If somehow we fetch more revisions than our journal 6858 // knows about, we should update our view of the 6859 // merged master, to avoid re-registering for the same 6860 // updates again. 6861 func() { 6862 fbo.headLock.Lock(lState) 6863 defer fbo.headLock.Unlock(lState) 6864 fbo.setLatestMergedRevisionLocked( 6865 ctx, lState, latestMerged.Revision(), false) 6866 }() 6867 } 6868 6869 fbo.log.CDebugf(ctx, 6870 "Ignoring fetched revisions while MDs are in journal") 6871 return nil 6872 } 6873 } 6874 6875 // Kick off partial prefetching once the latest merged revision is 6876 // set. 6877 oneApplied := false 6878 defer func() { 6879 if oneApplied && err == nil { 6880 fbo.kickOffPartialSyncIfNeeded(ctx, lState, latestMerged) 6881 } 6882 }() 6883 6884 fbo.headLock.Lock(lState) 6885 defer fbo.headLock.Unlock(lState) 6886 6887 // if we have staged changes, ignore all updates until conflict 6888 // resolution kicks in. TODO: cache these for future use. 6889 if fbo.isUnmergedLocked(lState) { 6890 // Don't trust un-put updates here because they might have 6891 // come from our own journal before the conflict was 6892 // detected. Assume we'll hear about the conflict via 6893 // callbacks from the journal. 6894 if !latestMerged.putToServer { 6895 return UnmergedError{} 6896 } 6897 6898 // setHeadLocked takes care of merged case 6899 fbo.setLatestMergedRevisionLocked( 6900 ctx, lState, latestMerged.Revision(), false) 6901 6902 unmergedRev := kbfsmd.RevisionUninitialized 6903 if fbo.head != (ImmutableRootMetadata{}) { 6904 unmergedRev = fbo.head.Revision() 6905 } 6906 fbo.cr.Resolve(ctx, unmergedRev, latestMerged.Revision()) 6907 return UnmergedError{} 6908 } 6909 6910 // Kick off a fetch of the latest root directory block, to make 6911 // sure we have it locally before we expose these changes to the 6912 // user. That way, if we go offline we can be reasonably sure the 6913 // user can at least list the root directory. 6914 latestRootBlockFetch := fbo.kickOffRootBlockFetch(ctx, latestMerged) 6915 6916 // Don't allow updates while we're in the dirty state; the next 6917 // sync will put us into an unmerged state anyway and we'll 6918 // require conflict resolution. 6919 if fbo.blocks.GetState(lState) != cleanState { 6920 return errors.WithStack(NoUpdatesWhileDirtyError{}) 6921 } 6922 6923 for i, rmd := range rmds { 6924 // check that we're applying the expected MD revision 6925 if rmd.Revision() <= fbo.getCurrMDRevisionLocked(lState) { 6926 // Already caught up! 6927 continue 6928 } 6929 err := isReadableOrError( 6930 ctx, fbo.config.KBPKI(), fbo.config, rmd.ReadOnly()) 6931 if err != nil { 6932 return err 6933 } 6934 6935 if i == len(rmds)-1 { 6936 _, _, err := fbo.waitForRootBlockFetchAndSyncIfNeeded( 6937 ctx, latestMerged, latestRootBlockFetch, nil) 6938 if err != nil { 6939 return err 6940 } 6941 } 6942 6943 err = fbo.setHeadSuccessorLocked(ctx, lState, rmd, false) 6944 if err != nil { 6945 return err 6946 } 6947 oneApplied = true 6948 // No new operations in these. 6949 if rmd.IsWriterMetadataCopiedSet() { 6950 continue 6951 } 6952 for _, op := range rmd.data.Changes.Ops { 6953 err := fbo.notifyOneOpLocked(ctx, lState, op, rmd.ReadOnly(), true) 6954 if err != nil { 6955 return err 6956 } 6957 } 6958 if rmd.IsRekeySet() { 6959 // One might have concern that a MD update written by the device 6960 // itself can slip in here, for example during the rekey after 6961 // setting paper prompt, and the event may cause the paper prompt 6962 // to be unset. This is not a problem because 1) the revision check 6963 // above shouldn't allow MD update written by this device to reach 6964 // here; 2) the rekey FSM doesn't touch anything if it has the 6965 // paper prompt set and is in scheduled state. 6966 fbo.rekeyFSM.Event(NewRekeyRequestEvent()) 6967 } else { 6968 fbo.rekeyFSM.Event(NewRekeyNotNeededEvent()) 6969 } 6970 } 6971 return nil 6972 } 6973 6974 func (fbo *folderBranchOps) undoMDUpdatesLocked(ctx context.Context, 6975 lState *kbfssync.LockState, rmds []ImmutableRootMetadata) error { 6976 fbo.mdWriterLock.AssertLocked(lState) 6977 6978 fbo.headLock.Lock(lState) 6979 defer fbo.headLock.Unlock(lState) 6980 6981 // Don't allow updates while we're in the dirty state; the next 6982 // sync will put us into an unmerged state anyway and we'll 6983 // require conflict resolution. 6984 if fbo.blocks.GetState(lState) != cleanState { 6985 return NotPermittedWhileDirtyError{} 6986 } 6987 6988 // go backwards through the updates 6989 for i := len(rmds) - 1; i >= 0; i-- { 6990 rmd := rmds[i] 6991 // on undo, it's ok to re-apply the current revision since you 6992 // need to invert all of its ops. 6993 // 6994 // This duplicates a check in 6995 // fbo.setHeadPredecessorLocked. TODO: Remove this 6996 // duplication. 6997 if rmd.Revision() != fbo.getCurrMDRevisionLocked(lState) && 6998 rmd.Revision() != fbo.getCurrMDRevisionLocked(lState)-1 { 6999 return MDUpdateInvertError{rmd.Revision(), 7000 fbo.getCurrMDRevisionLocked(lState)} 7001 } 7002 7003 // TODO: Check that the revisions are equal only for 7004 // the first iteration. 7005 if rmd.Revision() < fbo.getCurrMDRevisionLocked(lState) { 7006 err := fbo.setHeadPredecessorLocked(ctx, lState, rmd) 7007 if err != nil { 7008 return err 7009 } 7010 } 7011 7012 // iterate the ops in reverse and invert each one 7013 ops := rmd.data.Changes.Ops 7014 for j := len(ops) - 1; j >= 0; j-- { 7015 io, err := invertOpForLocalNotifications(ops[j]) 7016 if err != nil { 7017 fbo.log.CWarningf(ctx, 7018 "got error %v when invert op %v; "+ 7019 "skipping. Open file handles "+ 7020 "may now be in an invalid "+ 7021 "state, which can be fixed by "+ 7022 "either closing them all or "+ 7023 "restarting KBFS.", 7024 err, ops[j]) 7025 continue 7026 } 7027 err = fbo.notifyOneOpLocked(ctx, lState, io, rmd.ReadOnly(), false) 7028 if err != nil { 7029 return err 7030 } 7031 } 7032 } 7033 // TODO: update the edit history? 7034 return nil 7035 } 7036 7037 func (fbo *folderBranchOps) applyMDUpdates(ctx context.Context, 7038 lState *kbfssync.LockState, rmds []ImmutableRootMetadata) error { 7039 fbo.mdWriterLock.Lock(lState) 7040 defer fbo.mdWriterLock.Unlock(lState) 7041 return fbo.applyMDUpdatesLocked(ctx, lState, rmds) 7042 } 7043 7044 func (fbo *folderBranchOps) getLatestMergedRevision( 7045 lState *kbfssync.LockState) kbfsmd.Revision { 7046 fbo.headLock.RLock(lState) 7047 defer fbo.headLock.RUnlock(lState) 7048 return fbo.latestMergedRevision 7049 } 7050 7051 func (fbo *folderBranchOps) getLatestMergedMD( 7052 ctx context.Context, lState *kbfssync.LockState) ( 7053 ImmutableRootMetadata, error) { 7054 rev := fbo.getLatestMergedRevision(lState) 7055 if rev == kbfsmd.RevisionUninitialized { 7056 return ImmutableRootMetadata{}, nil 7057 } 7058 return GetSingleMD( 7059 ctx, fbo.config, fbo.id(), kbfsmd.NullBranchID, rev, kbfsmd.Merged, nil) 7060 } 7061 7062 // caller should have held fbo.headLock 7063 func (fbo *folderBranchOps) setLatestMergedRevisionLocked( 7064 ctx context.Context, lState *kbfssync.LockState, rev kbfsmd.Revision, 7065 allowBackward bool) { 7066 fbo.headLock.AssertLocked(lState) 7067 if rev == kbfsmd.RevisionUninitialized { 7068 panic("Cannot set latest merged revision to an uninitialized value") 7069 } 7070 7071 if fbo.latestMergedRevision < rev || allowBackward { 7072 fbo.latestMergedRevision = rev 7073 fbo.vlog.CLogf( 7074 ctx, libkb.VLog1, "Updated latestMergedRevision to %d.", rev) 7075 } else { 7076 fbo.log.CDebugf(ctx, "Local latestMergedRevision (%d) is higher than "+ 7077 "the new revision (%d); won't update.", fbo.latestMergedRevision, rev) 7078 } 7079 7080 if fbo.latestMergedUpdated != nil { 7081 close(fbo.latestMergedUpdated) 7082 } 7083 fbo.latestMergedUpdated = make(chan struct{}) 7084 fbo.fbm.signalLatestMergedRevision() 7085 } 7086 7087 // Assumes all necessary locking is either already done by caller, or 7088 // is done by applyFunc. 7089 func (fbo *folderBranchOps) getAndApplyMDUpdates(ctx context.Context, 7090 lState *kbfssync.LockState, lockBeforeGet *keybase1.LockID, 7091 applyFunc applyMDUpdatesFunc) error { 7092 // first look up all MD revisions newer than my current head 7093 start := fbo.getLatestMergedRevision(lState) + 1 7094 rmds, err := getMergedMDUpdates(ctx, 7095 fbo.config, fbo.id(), start, lockBeforeGet) 7096 if err != nil { 7097 return err 7098 } 7099 7100 err = applyFunc(ctx, lState, rmds) 7101 if err != nil { 7102 return err 7103 } 7104 return nil 7105 } 7106 7107 func (fbo *folderBranchOps) getAndApplyNewestUnmergedHead(ctx context.Context, 7108 lState *kbfssync.LockState) error { 7109 fbo.vlog.CLogf(ctx, libkb.VLog1, "Fetching the newest unmerged head") 7110 unmergedBID := func() kbfsmd.BranchID { 7111 fbo.mdWriterLock.Lock(lState) 7112 defer fbo.mdWriterLock.Unlock(lState) 7113 return fbo.unmergedBID 7114 }() 7115 7116 // We can only ever be at most one revision behind, so fetch the 7117 // latest unmerged revision and apply it as a successor. 7118 md, err := fbo.config.MDOps().GetUnmergedForTLF(ctx, fbo.id(), unmergedBID) 7119 if err != nil { 7120 return err 7121 } 7122 7123 if md == (ImmutableRootMetadata{}) { 7124 // There is no unmerged revision, oops! 7125 return errors.New("Couldn't find an unmerged head") 7126 } 7127 7128 fbo.mdWriterLock.Lock(lState) 7129 defer fbo.mdWriterLock.Unlock(lState) 7130 if fbo.unmergedBID != unmergedBID { 7131 // The branches switched (apparently CR completed), so just 7132 // try again. 7133 fbo.vlog.CLogf( 7134 ctx, libkb.VLog1, "Branches switched while fetching unmerged head") 7135 return nil 7136 } 7137 7138 fbo.headLock.Lock(lState) 7139 defer fbo.headLock.Unlock(lState) 7140 if err := fbo.setHeadSuccessorLocked(ctx, lState, md, false); err != nil { 7141 return err 7142 } 7143 if err := fbo.notifyBatchLocked(ctx, lState, md); err != nil { 7144 return err 7145 } 7146 return fbo.config.MDCache().Put(md) 7147 } 7148 7149 // getUnmergedMDUpdates returns a slice of the unmerged MDs for this 7150 // TLF's current unmerged branch and unmerged branch, between the 7151 // merge point for the branch and the current head. The returned MDs 7152 // are the same instances that are stored in the MD cache, so they 7153 // should be modified with care. 7154 func (fbo *folderBranchOps) getUnmergedMDUpdates( 7155 ctx context.Context, lState *kbfssync.LockState) ( 7156 kbfsmd.Revision, []ImmutableRootMetadata, error) { 7157 // acquire mdWriterLock to read the current branch ID. 7158 unmergedBID := func() kbfsmd.BranchID { 7159 fbo.mdWriterLock.Lock(lState) 7160 defer fbo.mdWriterLock.Unlock(lState) 7161 return fbo.unmergedBID 7162 }() 7163 return getUnmergedMDUpdates(ctx, fbo.config, fbo.id(), 7164 unmergedBID, fbo.getCurrMDRevision(lState)) 7165 } 7166 7167 func (fbo *folderBranchOps) getUnmergedMDUpdatesLocked( 7168 ctx context.Context, lState *kbfssync.LockState) ( 7169 kbfsmd.Revision, []ImmutableRootMetadata, error) { 7170 fbo.mdWriterLock.AssertLocked(lState) 7171 7172 return getUnmergedMDUpdates(ctx, fbo.config, fbo.id(), 7173 fbo.unmergedBID, fbo.getCurrMDRevision(lState)) 7174 } 7175 7176 // Returns a list of block pointers that were created during the 7177 // staged era. 7178 func (fbo *folderBranchOps) undoUnmergedMDUpdatesLocked( 7179 ctx context.Context, lState *kbfssync.LockState) ([]data.BlockPointer, error) { 7180 fbo.mdWriterLock.AssertLocked(lState) 7181 7182 currHead, unmergedRmds, err := fbo.getUnmergedMDUpdatesLocked(ctx, lState) 7183 if err != nil { 7184 return nil, err 7185 } 7186 7187 err = fbo.undoMDUpdatesLocked(ctx, lState, unmergedRmds) 7188 if err != nil { 7189 return nil, err 7190 } 7191 7192 // We have arrived at the branch point. The new root is 7193 // the previous revision from the current head. Find it 7194 // and apply. TODO: somehow fake the current head into 7195 // being currHead-1, so that future calls to 7196 // applyMDUpdates will fetch this along with the rest of 7197 // the updates. 7198 fbo.setBranchIDLocked(lState, kbfsmd.NullBranchID) 7199 7200 rmd, err := GetSingleMD(ctx, fbo.config, fbo.id(), kbfsmd.NullBranchID, 7201 currHead, kbfsmd.Merged, nil) 7202 if err != nil { 7203 return nil, err 7204 } 7205 err = func() error { 7206 fbo.headLock.Lock(lState) 7207 defer fbo.headLock.Unlock(lState) 7208 err = fbo.setHeadPredecessorLocked(ctx, lState, rmd) 7209 if err != nil { 7210 return err 7211 } 7212 fbo.setLatestMergedRevisionLocked(ctx, lState, rmd.Revision(), true) 7213 return nil 7214 }() 7215 if err != nil { 7216 return nil, err 7217 } 7218 7219 // Return all new refs 7220 var unmergedPtrs []data.BlockPointer 7221 for _, rmd := range unmergedRmds { 7222 for _, op := range rmd.data.Changes.Ops { 7223 for _, ptr := range op.Refs() { 7224 if ptr != data.ZeroPtr { 7225 unflushed, err := fbo.config.BlockServer().IsUnflushed( 7226 ctx, fbo.id(), ptr.ID) 7227 if err != nil { 7228 return nil, err 7229 } 7230 if !unflushed { 7231 unmergedPtrs = append(unmergedPtrs, ptr) 7232 } 7233 } 7234 } 7235 for _, update := range op.allUpdates() { 7236 if update.Ref != data.ZeroPtr { 7237 unflushed, err := fbo.config.BlockServer().IsUnflushed( 7238 ctx, fbo.id(), update.Ref.ID) 7239 if err != nil { 7240 return nil, err 7241 } 7242 if !unflushed { 7243 unmergedPtrs = append(unmergedPtrs, update.Ref) 7244 } 7245 } 7246 } 7247 } 7248 } 7249 7250 return unmergedPtrs, nil 7251 } 7252 7253 type pruningBehavior int 7254 7255 const ( 7256 doPruneBranches pruningBehavior = iota 7257 moveJournalsAway 7258 ) 7259 7260 func (fbo *folderBranchOps) unstageLocked(ctx context.Context, 7261 lState *kbfssync.LockState, pruningBehavior pruningBehavior) error { 7262 fbo.mdWriterLock.AssertLocked(lState) 7263 7264 // fetch all of my unstaged updates, and undo them one at a time 7265 unmergedBID, wasUnmergedBranch := 7266 fbo.unmergedBID, fbo.isUnmergedLocked(lState) 7267 unmergedPtrs, err := fbo.undoUnmergedMDUpdatesLocked(ctx, lState) 7268 if err != nil { 7269 return err 7270 } 7271 7272 // let the server know we no longer have need 7273 if wasUnmergedBranch && pruningBehavior == doPruneBranches { 7274 err = fbo.config.MDOps().PruneBranch(ctx, fbo.id(), unmergedBID) 7275 if err != nil { 7276 return err 7277 } 7278 } else if pruningBehavior == moveJournalsAway { 7279 jManager, err := GetJournalManager(fbo.config) 7280 if err != nil { 7281 return err 7282 } 7283 7284 err = jManager.MoveAway(ctx, fbo.id()) 7285 if err != nil { 7286 return err 7287 } 7288 } 7289 7290 currHead, err := fbo.config.MDOps().GetForTLF(ctx, fbo.id(), nil) 7291 if err != nil { 7292 return err 7293 } 7294 7295 ffDone, err := fbo.maybeFastForwardLocked(ctx, lState, currHead) 7296 if err != nil { 7297 return err 7298 } 7299 7300 if !ffDone { 7301 // now go forward in time, if possible 7302 err = fbo.getAndApplyMDUpdates(ctx, lState, nil, 7303 fbo.applyMDUpdatesLocked) 7304 if err != nil { 7305 return err 7306 } 7307 } 7308 7309 md, err := fbo.getSuccessorMDForWriteLocked(ctx, lState) 7310 if err != nil { 7311 return err 7312 } 7313 7314 // Finally, create a resolutionOp with the newly-unref'd pointers. 7315 resOp := newResolutionOp() 7316 for _, ptr := range unmergedPtrs { 7317 resOp.AddUnrefBlock(ptr) 7318 } 7319 md.AddOp(resOp) 7320 7321 bps, err := fbo.maybeUnembedAndPutBlocks(ctx, md) 7322 if err != nil { 7323 return err 7324 } 7325 7326 return fbo.finalizeMDWriteLocked(ctx, lState, md, bps, NoExcl, 7327 func(md ImmutableRootMetadata) error { 7328 return fbo.notifyBatchLocked(ctx, lState, md) 7329 }) 7330 } 7331 7332 // TODO: remove once we have automatic conflict resolution 7333 func (fbo *folderBranchOps) UnstageForTesting( 7334 ctx context.Context, folderBranch data.FolderBranch) (err error) { 7335 startTime, timer := fbo.startOp(ctx, "UnstageForTesting") 7336 defer func() { 7337 fbo.endOp(ctx, startTime, timer, "UnstageForTesting done: %+v", err) 7338 }() 7339 7340 if folderBranch != fbo.folderBranch { 7341 return WrongOpsError{fbo.folderBranch, folderBranch} 7342 } 7343 7344 return runUnlessCanceled(ctx, func() error { 7345 lState := makeFBOLockState() 7346 7347 if !fbo.isUnmerged(lState) { 7348 // no-op 7349 return nil 7350 } 7351 7352 if fbo.blocks.GetState(lState) != cleanState { 7353 return NotPermittedWhileDirtyError{} 7354 } 7355 7356 // launch unstaging in a new goroutine, because we don't want to 7357 // use the provided context because upper layers might ignore our 7358 // notifications if we do. But we still want to wait for the 7359 // context to cancel. 7360 c := make(chan error, 1) 7361 freshCtx, cancel := fbo.newCtxWithFBOID() 7362 defer cancel() 7363 fbo.log.CDebugf(freshCtx, "Launching new context for UnstageForTesting") 7364 fbo.goTracked(func() { 7365 lState := makeFBOLockState() 7366 c <- fbo.doMDWriteWithRetry(ctx, lState, 7367 func(lState *kbfssync.LockState) error { 7368 return fbo.unstageLocked(freshCtx, lState, doPruneBranches) 7369 }) 7370 }) 7371 7372 select { 7373 case err := <-c: 7374 return err 7375 case <-ctx.Done(): 7376 return ctx.Err() 7377 } 7378 }) 7379 } 7380 7381 func (fbo *folderBranchOps) cancelUploadsLocked( 7382 ctx context.Context, lState *kbfssync.LockState) error { 7383 fbo.mdWriterLock.AssertLocked(lState) 7384 7385 jManager, _ := GetJournalManager(fbo.config) 7386 if jManager == nil || !jManager.JournalEnabled(fbo.id()) { 7387 return errors.New("Journal not enabled") 7388 } 7389 7390 // For now, don't allow cancelling when we're in conflict mode. 7391 // In the future though, maybe this should also cancel conflict 7392 // resolution and clear any stuck conflicts. 7393 if fbo.isUnmergedLocked(lState) { 7394 return errors.New("Can't cancel uploads while there's a conflict") 7395 } 7396 7397 // Pause the uploads right away. 7398 jManager.PauseBackgroundWork(ctx, fbo.id()) 7399 // Wait until the pause takes effect. 7400 err := jManager.Wait(ctx, fbo.id()) 7401 if err != nil { 7402 return err 7403 } 7404 7405 // Get all the MDs between the latest merged revision and the head 7406 // of the journal. 7407 latestMerged := fbo.getLatestMergedRevision(lState) 7408 rmds, err := getMergedMDUpdates( 7409 ctx, fbo.config, fbo.id(), latestMerged+1, nil) 7410 if err != nil { 7411 return err 7412 } 7413 7414 if len(rmds) > 0 { 7415 fbo.log.CDebugf( 7416 ctx, "Undoing MD updates [%d:%d]", rmds[0].Revision(), 7417 rmds[len(rmds)-1].Revision()) 7418 err = fbo.undoMDUpdatesLocked(ctx, lState, rmds) 7419 if err != nil { 7420 return err 7421 } 7422 7423 // The latest merged MD becomes the new head. 7424 rmd, err := GetSingleMD(ctx, fbo.config, fbo.id(), kbfsmd.NullBranchID, 7425 latestMerged, kbfsmd.Merged, nil) 7426 if err != nil { 7427 return err 7428 } 7429 err = func() error { 7430 fbo.headLock.Lock(lState) 7431 defer fbo.headLock.Unlock(lState) 7432 err = fbo.setHeadPredecessorLocked(ctx, lState, rmd) 7433 if err != nil { 7434 return err 7435 } 7436 return nil 7437 }() 7438 if err != nil { 7439 return err 7440 } 7441 7442 // Clear all the un-uploaded MDs from the in-memory cache. 7443 for _, rmd := range rmds { 7444 fbo.config.MDCache().Delete(fbo.id(), rmd.Revision(), 7445 kbfsmd.NullBranchID) 7446 } 7447 } 7448 7449 err = jManager.DeleteJournal(ctx, fbo.id()) 7450 if err != nil { 7451 return err 7452 } 7453 7454 md, _ := fbo.getHead(ctx, lState, mdNoCommit) 7455 if md == (ImmutableRootMetadata{}) { 7456 return errors.New("No MD") 7457 } 7458 7459 // Now turn the journal back on. 7460 return jManager.Enable( 7461 ctx, fbo.id(), md.GetTlfHandle(), TLFJournalBackgroundWorkEnabled) 7462 } 7463 7464 // CancelUploads implements the KBFSOps interface for folderBranchOps. 7465 func (fbo *folderBranchOps) CancelUploads( 7466 ctx context.Context, folderBranch data.FolderBranch) (err error) { 7467 startTime, timer := fbo.startOp(ctx, "CancelUploads") 7468 defer func() { 7469 fbo.endOp(ctx, startTime, timer, "CancelUploads done: %+v", err) 7470 }() 7471 7472 if folderBranch != fbo.folderBranch { 7473 return WrongOpsError{fbo.folderBranch, folderBranch} 7474 } 7475 7476 // Launch cancellation in a new goroutine, because we don't 7477 // want to use the provided context because upper layers might 7478 // ignore our notifications if we do. But we still want to 7479 // wait for the context to cancel. 7480 c := make(chan error, 1) 7481 freshCtx, cancel := fbo.newCtxWithFBOID() 7482 defer cancel() 7483 fbo.log.CDebugf( 7484 ctx, "Launching new context for CancelUploads: %s", 7485 freshCtx.Value(CtxFBOIDKey)) 7486 fbo.goTracked(func() { 7487 lState := makeFBOLockState() 7488 c <- fbo.doMDWriteWithRetry(ctx, lState, 7489 func(lState *kbfssync.LockState) error { 7490 return fbo.cancelUploadsLocked(freshCtx, lState) 7491 }) 7492 }) 7493 7494 select { 7495 case err := <-c: 7496 return err 7497 case <-ctx.Done(): 7498 return ctx.Err() 7499 } 7500 } 7501 7502 // mdWriterLock must be taken by the caller. 7503 func (fbo *folderBranchOps) rekeyLocked(ctx context.Context, 7504 lState *kbfssync.LockState, promptPaper bool) (res RekeyResult, err error) { 7505 startTime, timer := fbo.startOp(ctx, "rekeyLocked") 7506 defer func() { 7507 fbo.endOp(ctx, startTime, timer, "rekeyLocked done: %+v %+v", res, err) 7508 }() 7509 7510 fbo.mdWriterLock.AssertLocked(lState) 7511 7512 if fbo.isUnmergedLocked(lState) { 7513 return RekeyResult{}, errors.New("can't rekey while staged") 7514 } 7515 7516 // untrusted head is ok here. 7517 head, _ := fbo.getHead(ctx, lState, mdNoCommit) 7518 if head != (ImmutableRootMetadata{}) { 7519 // If we already have a cached revision, make sure we're 7520 // up-to-date with the latest revision before inspecting the 7521 // metadata, since Rekey doesn't let us go into CR mode, and 7522 // we don't actually get folder update notifications when the 7523 // rekey bit is set, just a "folder needs rekey" update. 7524 if err := fbo.getAndApplyMDUpdates( 7525 ctx, lState, nil, fbo.applyMDUpdatesLocked); err != nil { 7526 if applyErr, ok := err.(kbfsmd.MDRevisionMismatch); !ok || 7527 applyErr.Rev != applyErr.Curr { 7528 return RekeyResult{}, err 7529 } 7530 } 7531 7532 head, _ = fbo.getHead(ctx, lState, mdNoCommit) 7533 if head.TypeForKeying() == tlf.TeamKeying { 7534 fbo.vlog.CLogf(ctx, libkb.VLog1, "A team TLF doesn't need a rekey") 7535 return RekeyResult{}, nil 7536 } 7537 } 7538 7539 md, lastWriterVerifyingKey, rekeyWasSet, err := 7540 fbo.getMDForRekeyWriteLocked(ctx, lState) 7541 if err != nil { 7542 return RekeyResult{}, err 7543 } 7544 if md == nil { 7545 fbo.log.CDebugf(ctx, "A team TLF doesn't need a rekey") 7546 return RekeyResult{}, nil 7547 } 7548 7549 currKeyGen := md.LatestKeyGeneration() 7550 rekeyDone, tlfCryptKey, err := fbo.config.KeyManager(). 7551 Rekey(ctx, md, promptPaper) 7552 7553 stillNeedsRekey := false 7554 switch err.(type) { 7555 case nil: 7556 // TODO: implement a "forced" option that rekeys even when the 7557 // devices haven't changed? 7558 if !rekeyDone { 7559 fbo.log.CDebugf(ctx, "No rekey necessary") 7560 return RekeyResult{ 7561 DidRekey: false, 7562 NeedsPaperKey: false, 7563 }, nil 7564 } 7565 // Clear the rekey bit if any. 7566 md.clearRekeyBit() 7567 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 7568 if err != nil { 7569 return RekeyResult{}, err 7570 } 7571 // Readers can't clear the last revision, because: 7572 // 1) They don't have access to the writer metadata, so can't clear the 7573 // block changes. 7574 // 2) Readers need the kbfsmd.MetadataFlagWriterMetadataCopied bit set for 7575 // MDServer to authorize the write. 7576 // Without this check, MDServer returns an Unauthorized error. 7577 if md.GetTlfHandle().IsWriter(session.UID) { 7578 md.clearLastRevision() 7579 } 7580 7581 case RekeyIncompleteError: 7582 if !rekeyDone && rekeyWasSet { 7583 // The rekey bit was already set, and there's nothing else 7584 // we can to do, so don't put any new revisions. 7585 fbo.log.CDebugf(ctx, "No further rekey possible by this user.") 7586 return RekeyResult{ 7587 DidRekey: false, 7588 NeedsPaperKey: false, 7589 }, nil 7590 } 7591 7592 // Rekey incomplete, fallthrough without early exit, to ensure 7593 // we write the metadata with any potential changes 7594 fbo.log.CDebugf(ctx, 7595 "Rekeyed reader devices, but still need writer rekey") 7596 7597 case NeedOtherRekeyError, NeedSelfRekeyError: 7598 stillNeedsRekey = true 7599 7600 default: 7601 _, isInputCanceled := err.(libkb.InputCanceledError) 7602 if isInputCanceled || err == context.DeadlineExceeded { 7603 fbo.log.CDebugf(ctx, "Paper key prompt timed out") 7604 // Reschedule the prompt in the timeout case. 7605 stillNeedsRekey = true 7606 } else { 7607 return RekeyResult{}, err 7608 } 7609 } 7610 7611 if stillNeedsRekey { 7612 fbo.log.CDebugf(ctx, "Device doesn't have access to rekey") 7613 // If we didn't have read access, then we don't have any 7614 // unlocked paper keys. Wait for some time, and then if we 7615 // still aren't rekeyed, try again but this time prompt the 7616 // user for any known paper keys. We do this even if the 7617 // rekey bit is already set, since we may have restarted since 7618 // the previous rekey attempt, before prompting for the paper 7619 // key. Only schedule this as a one-time event, since direct 7620 // folder accesses from the user will also cause a 7621 // rekeyWithPrompt. 7622 7623 if rekeyWasSet { 7624 // Devices not yet keyed shouldn't set the rekey bit again 7625 fbo.log.CDebugf(ctx, "Rekey bit already set") 7626 return RekeyResult{ 7627 DidRekey: rekeyDone, 7628 NeedsPaperKey: true, 7629 }, nil 7630 } 7631 // This device hasn't been keyed yet, fall through to set the rekey bit 7632 } 7633 7634 // add an empty operation to satisfy assumptions elsewhere 7635 md.AddOp(newRekeyOp()) 7636 7637 // we still let readers push a new md block that we validate against reader 7638 // permissions 7639 err = fbo.finalizeMDRekeyWriteLocked( 7640 ctx, lState, md, lastWriterVerifyingKey) 7641 if err != nil { 7642 return RekeyResult{ 7643 DidRekey: rekeyDone, 7644 NeedsPaperKey: stillNeedsRekey, 7645 }, err 7646 } 7647 7648 // cache any new TLF crypt key 7649 if tlfCryptKey != nil { 7650 keyGen := md.LatestKeyGeneration() 7651 err = fbo.config.KeyCache().PutTLFCryptKey(md.TlfID(), keyGen, *tlfCryptKey) 7652 if err != nil { 7653 return RekeyResult{ 7654 DidRekey: rekeyDone, 7655 NeedsPaperKey: stillNeedsRekey, 7656 }, err 7657 } 7658 } 7659 7660 // send rekey finish notification 7661 handle := md.GetTlfHandle() 7662 if currKeyGen >= kbfsmd.FirstValidKeyGen && rekeyDone { 7663 fbo.config.Reporter().Notify(ctx, 7664 rekeyNotification(ctx, fbo.config, handle, true)) 7665 } 7666 7667 return RekeyResult{ 7668 DidRekey: rekeyDone, 7669 NeedsPaperKey: stillNeedsRekey, 7670 }, nil 7671 } 7672 7673 func (fbo *folderBranchOps) RequestRekey(_ context.Context, tlf tlf.ID) { 7674 // Only the MasterBranch can be rekeyed. 7675 fb := data.FolderBranch{Tlf: tlf, Branch: data.MasterBranch} 7676 if fb != fbo.folderBranch { 7677 // TODO: log instead of panic? 7678 panic(WrongOpsError{fbo.folderBranch, fb}) 7679 } 7680 fbo.rekeyFSM.Event(NewRekeyRequestEvent()) 7681 } 7682 7683 func (fbo *folderBranchOps) syncAllForServerSync( 7684 ctx context.Context, lState *kbfssync.LockState) error { 7685 // Make sure everything outstanding syncs to disk at least. 7686 if err := fbo.syncAllUnlocked(ctx, lState); err != nil { 7687 return err 7688 } 7689 7690 // A journal flush before CR, if needed. 7691 if err := WaitForTLFJournal(ctx, fbo.config, fbo.id(), 7692 fbo.log); err != nil { 7693 return err 7694 } 7695 7696 if err := fbo.mdFlushes.Wait(ctx); err != nil { 7697 return err 7698 } 7699 7700 if err := fbo.branchChanges.Wait(ctx); err != nil { 7701 return err 7702 } 7703 7704 if err := fbo.rootWaits.Wait(ctx); err != nil { 7705 return err 7706 } 7707 7708 return nil 7709 } 7710 7711 func (fbo *folderBranchOps) SyncFromServer(ctx context.Context, 7712 folderBranch data.FolderBranch, lockBeforeGet *keybase1.LockID) (err error) { 7713 startTime, timer := fbo.startOp(ctx, "SyncFromServer") 7714 defer func() { 7715 fbo.endOp(ctx, startTime, timer, "SyncFromServer done: %+v", err) 7716 }() 7717 7718 if folderBranch != fbo.folderBranch { 7719 return WrongOpsError{fbo.folderBranch, folderBranch} 7720 } 7721 7722 lState := makeFBOLockState() 7723 7724 err = fbo.syncAllForServerSync(ctx, lState) 7725 if err != nil { 7726 return err 7727 } 7728 7729 // MDServer.IsConnected() takes some time to work when you get 7730 // disconnected from inside the network (as we do in a test). To 7731 // get a quick result, force a reachability check with a short 7732 // timeout, and if it times out, assume we're disconnected. 7733 mdserver := fbo.config.MDServer() 7734 timeoutCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) 7735 defer cancel() 7736 mdserver.CheckReachability(timeoutCtx) 7737 timedOut := false 7738 select { 7739 case <-timeoutCtx.Done(): 7740 timedOut = true 7741 default: 7742 } 7743 if lockBeforeGet == nil && (timedOut || !mdserver.IsConnected()) { 7744 fbo.vlog.CLogf( 7745 ctx, libkb.VLog1, "Not fetching new updates while offline") 7746 return nil 7747 } 7748 7749 // Loop until we're fully updated on the master branch. 7750 for { 7751 if fbo.isUnmerged(lState) { 7752 if err := fbo.cr.Wait(ctx); err != nil { 7753 return err 7754 } 7755 // If we are still staged after the wait, then we have a problem. 7756 if fbo.isUnmerged(lState) { 7757 return &ErrStillStagedAfterCR{} 7758 } 7759 } 7760 7761 dirtyFiles := fbo.blocks.GetDirtyFileBlockRefs(lState) 7762 if len(dirtyFiles) > 0 { 7763 for _, ref := range dirtyFiles { 7764 fbo.vlog.CLogf(ctx, libkb.VLog1, "DeCache entry left: %v", ref) 7765 } 7766 fbo.log.CDebugf(ctx, "Can't sync from server while dirty; retrying") 7767 err := fbo.syncAllForServerSync(ctx, lState) 7768 if err != nil { 7769 return err 7770 } 7771 continue 7772 } 7773 7774 // A journal flush after CR, if needed. 7775 if err := WaitForTLFJournal(ctx, fbo.config, fbo.id(), 7776 fbo.log); err != nil { 7777 return err 7778 } 7779 7780 if err := fbo.mdFlushes.Wait(ctx); err != nil { 7781 return err 7782 } 7783 7784 if err := fbo.branchChanges.Wait(ctx); err != nil { 7785 return err 7786 } 7787 7788 if err := fbo.getAndApplyMDUpdates( 7789 ctx, lState, lockBeforeGet, fbo.applyMDUpdates); err != nil { 7790 if applyErr, ok := err.(kbfsmd.MDRevisionMismatch); ok { 7791 if applyErr.Rev == applyErr.Curr { 7792 fbo.vlog.CLogf( 7793 ctx, libkb.VLog1, "Already up-to-date with server") 7794 return nil 7795 } 7796 } 7797 if _, isUnmerged := err.(UnmergedError); isUnmerged { 7798 continue 7799 } else if err == errNoMergedRevWhileStaged { 7800 continue 7801 } 7802 return err 7803 } 7804 break 7805 } 7806 7807 // Wait for all the asynchronous block archiving and quota 7808 // reclamation to hit the block server. 7809 if err := fbo.fbm.waitForArchives(ctx); err != nil { 7810 return err 7811 } 7812 if err := fbo.fbm.waitForDeletingBlocks(ctx); err != nil { 7813 return err 7814 } 7815 7816 // For tests where the service is offline, we can't wait forever 7817 // for the edit activity to complete. 7818 editCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) 7819 defer cancel() 7820 if err := fbo.editActivity.Wait(editCtx); err != nil { 7821 if err == context.DeadlineExceeded { 7822 fbo.log.CDebugf(ctx, "Couldn't wait for edit activity") 7823 } else { 7824 return err 7825 } 7826 } 7827 7828 // Same with block-related activity, when the bserver might be offline. 7829 qrCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) 7830 defer cancel() 7831 if err := fbo.fbm.waitForQuotaReclamations(qrCtx); err != nil { 7832 if err == context.DeadlineExceeded { 7833 fbo.log.CDebugf(ctx, "Couldn't wait for qr activity") 7834 } else { 7835 return err 7836 } 7837 } 7838 cleanCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) 7839 defer cancel() 7840 if err := fbo.fbm.waitForDiskCacheCleans(cleanCtx); err != nil { 7841 if err == context.DeadlineExceeded { 7842 fbo.log.CDebugf(ctx, "Couldn't wait for disk clean activity") 7843 } else { 7844 return err 7845 } 7846 } 7847 if err := fbo.partialSyncs.Wait(ctx); err != nil { 7848 return err 7849 } 7850 7851 // A second journal flush if needed, to clear out any 7852 // archive/remove calls caused by the above operations. 7853 return WaitForTLFJournal(ctx, fbo.config, fbo.id(), fbo.log) 7854 } 7855 7856 // CtxFBOTagKey is the type used for unique context tags within folderBranchOps 7857 type CtxFBOTagKey int 7858 7859 const ( 7860 // CtxFBOIDKey is the type of the tag for unique operation IDs 7861 // within folderBranchOps. 7862 CtxFBOIDKey CtxFBOTagKey = iota 7863 ) 7864 7865 // CtxFBOOpID is the display name for the unique operation 7866 // folderBranchOps ID tag. 7867 const CtxFBOOpID = "FBOID" 7868 7869 func (fbo *folderBranchOps) ctxWithFBOID(ctx context.Context) context.Context { 7870 return CtxWithRandomIDReplayable(ctx, CtxFBOIDKey, CtxFBOOpID, fbo.log) 7871 } 7872 7873 func (fbo *folderBranchOps) newCtxWithFBOIDWithCtx(ctx context.Context) ( 7874 context.Context, context.CancelFunc) { 7875 // No need to call NewContextReplayable since ctxWithFBOID calls 7876 // ctxWithRandomIDReplayable, which attaches replayably. 7877 ctx = fbo.ctxWithFBOID(ctx) 7878 ctx, cancelFunc := context.WithCancel(ctx) 7879 ctx, err := libcontext.NewContextWithCancellationDelayer(ctx) 7880 if err != nil { 7881 panic(err) 7882 } 7883 return ctx, cancelFunc 7884 } 7885 7886 func (fbo *folderBranchOps) newCtxWithFBOID() ( 7887 context.Context, context.CancelFunc) { 7888 return fbo.newCtxWithFBOIDWithCtx(context.Background()) 7889 } 7890 7891 // Run the passed function with a context that's canceled on shutdown. 7892 func (fbo *folderBranchOps) runUnlessShutdown(fn func(ctx context.Context) error) error { 7893 ctx, cancelFunc := fbo.newCtxWithFBOID() 7894 defer cancelFunc() 7895 errChan := make(chan error, 1) 7896 fbo.goTracked(func() { 7897 errChan <- fn(ctx) 7898 }) 7899 7900 select { 7901 case err := <-errChan: 7902 return err 7903 case <-fbo.shutdownChan: 7904 return data.ShutdownHappenedError{} 7905 } 7906 } 7907 7908 func (fbo *folderBranchOps) doFastForwardLocked(ctx context.Context, 7909 lState *kbfssync.LockState, currHead ImmutableRootMetadata) (err error) { 7910 fbo.mdWriterLock.AssertLocked(lState) 7911 fbo.headLock.AssertLocked(lState) 7912 7913 fbo.log.CDebugf(ctx, "Fast-forwarding from rev %d to rev %d", 7914 fbo.latestMergedRevision, currHead.Revision()) 7915 7916 // Fetch root block and set the head first, because if it fails we 7917 // don't want to have to undo a bunch of pointer updates. (That 7918 // is, follow the same order as when usually updating the head.) 7919 _, _, err = fbo.kickOffRootBlockFetchAndWait(ctx, currHead, nil) 7920 if err != nil { 7921 return err 7922 } 7923 7924 err = fbo.setHeadSuccessorLocked(ctx, lState, currHead, true /*rebase*/) 7925 if err != nil { 7926 return err 7927 } 7928 7929 defer func() { 7930 if err == nil { 7931 return 7932 } 7933 7934 // If updating the pointers failed, we need to revert the head 7935 // as well. 7936 fbo.log.CDebugf(ctx, "Fast-forward failed: %+v; reverting the head") 7937 revertErr := fbo.setHeadLocked( 7938 ctx, lState, currHead, headTrusted, mdToCommitType(currHead)) 7939 if revertErr != nil { 7940 fbo.log.CDebugf(ctx, "Couldn't revert head: %+v", err) 7941 } 7942 }() 7943 7944 changes, affectedNodeIDs, err := fbo.blocks.FastForwardAllNodes( 7945 ctx, lState, currHead.ReadOnly()) 7946 if err != nil { 7947 return err 7948 } 7949 7950 // Invalidate all the affected nodes. 7951 if len(changes) > 0 { 7952 fbo.observers.batchChanges(ctx, changes, affectedNodeIDs) 7953 } 7954 7955 return nil 7956 } 7957 7958 func (fbo *folderBranchOps) maybeFastForwardLocked( 7959 ctx context.Context, lState *kbfssync.LockState, 7960 currHead ImmutableRootMetadata) (fastForwardDone bool, err error) { 7961 fbo.mdWriterLock.AssertLocked(lState) 7962 7963 // Kick off partial prefetching once the latest merged 7964 // revision is set. 7965 defer func() { 7966 if err == nil { 7967 fbo.kickOffPartialSyncIfNeeded(ctx, lState, currHead) 7968 } 7969 }() 7970 7971 fbo.headLock.Lock(lState) 7972 defer fbo.headLock.Unlock(lState) 7973 7974 if currHead.Revision() < fbo.latestMergedRevision+fastForwardRevThresh { 7975 // Might as well fetch all the revisions. 7976 return false, nil 7977 } 7978 7979 err = fbo.doFastForwardLocked(ctx, lState, currHead) 7980 if err != nil { 7981 return false, err 7982 } 7983 return true, nil 7984 } 7985 7986 func (fbo *folderBranchOps) maybeFastForward(ctx context.Context, 7987 lState *kbfssync.LockState, lastUpdate time.Time, currUpdate time.Time) ( 7988 fastForwardDone bool, err error) { 7989 // Has it been long enough to try fast-forwarding? 7990 if currUpdate.Before(lastUpdate.Add(fastForwardTimeThresh)) || 7991 fbo.isUnmerged(lState) { 7992 return false, nil 7993 } 7994 7995 fbo.vlog.CLogf( 7996 ctx, libkb.VLog1, "Checking head for possible "+ 7997 "fast-forwarding (last update time=%s)", lastUpdate) 7998 currHead, err := fbo.config.MDOps().GetForTLF(ctx, fbo.id(), nil) 7999 if err != nil { 8000 return false, err 8001 } 8002 fbo.vlog.CLogf( 8003 ctx, libkb.VLog1, "Current head is revision %d", currHead.Revision()) 8004 8005 fbo.mdWriterLock.Lock(lState) 8006 defer fbo.mdWriterLock.Unlock(lState) 8007 // Don't update while the in-memory state is dirty. 8008 if fbo.blocks.GetState(lState) != cleanState { 8009 return false, nil 8010 } 8011 8012 // If the journal has anything in it, don't fast-forward since we 8013 // haven't finished flushing yet. If there was really a remote 8014 // update on the server, we'll end up in CR eventually. 8015 mergedRev, err := fbo.getJournalPredecessorRevision(ctx) 8016 if err != nil { 8017 return false, err 8018 } 8019 if mergedRev != kbfsmd.RevisionUninitialized { 8020 return false, nil 8021 } 8022 8023 if fbo.isUnmergedLocked(lState) { 8024 // Don't update if we're staged. 8025 return false, nil 8026 } 8027 8028 return fbo.maybeFastForwardLocked(ctx, lState, currHead) 8029 } 8030 8031 func (fbo *folderBranchOps) locallyFinalizeTLF(ctx context.Context) { 8032 lState := makeFBOLockState() 8033 fbo.mdWriterLock.Lock(lState) 8034 defer fbo.mdWriterLock.Unlock(lState) 8035 fbo.headLock.Lock(lState) 8036 defer fbo.headLock.Unlock(lState) 8037 8038 if fbo.head == (ImmutableRootMetadata{}) { 8039 return 8040 } 8041 8042 // It's safe to give this a finalized number of 1 and a fake user 8043 // name. The whole point here is to move the old finalized TLF 8044 // name away to a new name, where the user won't be able to access 8045 // it anymore, and if there's a conflict with a previously-moved 8046 // TLF that shouldn't matter. 8047 now := fbo.config.Clock().Now() 8048 finalizedInfo, err := tlf.NewHandleExtension( 8049 tlf.HandleExtensionFinalized, 1, kbname.NormalizedUsername("<unknown>"), 8050 now) 8051 if err != nil { 8052 fbo.log.CErrorf(ctx, "Couldn't make finalized info: %+v", err) 8053 return 8054 } 8055 8056 fakeSignedHead := &RootMetadataSigned{RootMetadataSigned: kbfsmd.RootMetadataSigned{MD: fbo.head.bareMd}} 8057 finalRmd, err := fakeSignedHead.MakeFinalCopy( 8058 fbo.config.Codec(), now, finalizedInfo) 8059 if err != nil { 8060 fbo.log.CErrorf(ctx, "Couldn't finalize MD: %+v", err) 8061 return 8062 } 8063 8064 // Construct the data needed to fake a new head. 8065 mdID, err := kbfsmd.MakeID(fbo.config.Codec(), finalRmd.MD) 8066 if err != nil { 8067 fbo.log.CErrorf(ctx, "Couldn't get finalized MD ID: %+v", err) 8068 return 8069 } 8070 bareHandle, err := finalRmd.MD.MakeBareTlfHandle(fbo.head.Extra()) 8071 if err != nil { 8072 fbo.log.CErrorf(ctx, "Couldn't get finalized bare handle: %+v", err) 8073 return 8074 } 8075 handle, err := tlfhandle.MakeHandle( 8076 ctx, bareHandle, fbo.id().Type(), fbo.config.KBPKI(), 8077 fbo.config.KBPKI(), fbo.config.MDOps(), fbo.oa()) 8078 if err != nil { 8079 fbo.log.CErrorf(ctx, "Couldn't get finalized handle: %+v", err) 8080 return 8081 } 8082 finalBrmd, ok := finalRmd.MD.(kbfsmd.MutableRootMetadata) 8083 if !ok { 8084 fbo.log.CErrorf(ctx, "Couldn't get finalized mutable bare MD: %+v", err) 8085 return 8086 } 8087 8088 // We don't have a way to sign this with a valid key (and we might 8089 // be logged out anyway), so just directly make the md immutable. 8090 finalIrmd := ImmutableRootMetadata{ 8091 ReadOnlyRootMetadata: makeRootMetadata( 8092 finalBrmd, fbo.head.Extra(), handle).ReadOnly(), 8093 mdID: mdID, 8094 } 8095 8096 // This will trigger the handle change notification to observers. 8097 err = fbo.setHeadSuccessorLocked(ctx, lState, finalIrmd, false) 8098 if err != nil { 8099 fbo.log.CErrorf(ctx, "Couldn't set finalized MD: %+v", err) 8100 return 8101 } 8102 } 8103 8104 func (fbo *folderBranchOps) registerAndWaitForUpdates() { 8105 defer close(fbo.updateDoneChan) 8106 childDone := make(chan struct{}) 8107 var lastUpdate time.Time 8108 err := fbo.runUnlessShutdown(func(ctx context.Context) error { 8109 defer close(childDone) 8110 // If we fail to register for or process updates, try again 8111 // with an exponential backoff, so we don't overwhelm the 8112 // server or ourselves with too many attempts in a hopeless 8113 // situation. 8114 expBackoff := backoff.NewExponentialBackOff() 8115 // Never give up hope until we shut down 8116 expBackoff.MaxElapsedTime = 0 8117 // Register and wait in a loop unless we hit an unrecoverable error 8118 fbo.cancelUpdatesLock.Lock() 8119 if fbo.cancelUpdates != nil { 8120 // It should be impossible to get here without having 8121 // already called the cancel function, but just in case 8122 // call it here again. 8123 fbo.cancelUpdates() 8124 } 8125 ctx, fbo.cancelUpdates = context.WithCancel(ctx) 8126 fbo.cancelUpdatesLock.Unlock() 8127 for { 8128 err := backoff.RetryNotifyWithContext(ctx, func() error { 8129 // Replace the FBOID one with a fresh id for every attempt 8130 newCtx := fbo.ctxWithFBOID(ctx) 8131 updateChan, err := fbo.registerForUpdates(newCtx) 8132 if err != nil { 8133 select { 8134 case <-ctx.Done(): 8135 // Shortcut the retry, we're done. 8136 return nil 8137 default: 8138 return err 8139 } 8140 } 8141 8142 currUpdate, err := fbo.waitForAndProcessUpdates( 8143 newCtx, lastUpdate, updateChan) 8144 switch errors.Cause(err).(type) { 8145 case UnmergedError: 8146 // skip the back-off timer and continue directly to next 8147 // registerForUpdates 8148 return nil 8149 case kbfsmd.NewMetadataVersionError: 8150 fbo.log.CDebugf(ctx, "Abandoning updates since we can't "+ 8151 "read the newest metadata: %+v", err) 8152 fbo.status.setPermErr(err) 8153 // No need to lock here, since `cancelUpdates` is 8154 // only set within this same goroutine. 8155 fbo.cancelUpdates() 8156 return context.Canceled 8157 case kbfsmd.ServerErrorCannotReadFinalizedTLF: 8158 fbo.log.CDebugf(ctx, "Abandoning updates since we can't "+ 8159 "read the finalized metadata for this TLF: %+v", err) 8160 fbo.status.setPermErr(err) 8161 8162 // Locally finalize the TLF so new accesses 8163 // through to the old folder name will find the 8164 // new folder. 8165 fbo.locallyFinalizeTLF(newCtx) 8166 8167 // No need to lock here, since `cancelUpdates` is 8168 // only set within this same goroutine. 8169 fbo.cancelUpdates() 8170 return context.Canceled 8171 } 8172 select { 8173 case <-ctx.Done(): 8174 // Shortcut the retry, we're done. 8175 return nil 8176 default: 8177 if err == nil { 8178 lastUpdate = currUpdate 8179 } 8180 return err 8181 } 8182 }, 8183 expBackoff, 8184 func(err error, nextTime time.Duration) { 8185 fbo.log.CDebugf(ctx, 8186 "Retrying registerForUpdates in %s due to err: %v", 8187 nextTime, err) 8188 }) 8189 if err != nil { 8190 return err 8191 } 8192 } 8193 }) 8194 8195 if err != nil && err != context.Canceled { 8196 fbo.log.CWarningf(context.Background(), 8197 "registerAndWaitForUpdates failed unexpectedly with an error: %v", 8198 err) 8199 } 8200 <-childDone 8201 } 8202 8203 func (fbo *folderBranchOps) registerForUpdatesShouldFireNow() bool { 8204 fbo.muLastGetHead.Lock() 8205 defer fbo.muLastGetHead.Unlock() 8206 return fbo.config.Clock().Now().Sub(fbo.lastGetHead) < registerForUpdatesFireNowThreshold 8207 } 8208 8209 func (fbo *folderBranchOps) registerForUpdates(ctx context.Context) ( 8210 updateChan <-chan error, err error) { 8211 lState := makeFBOLockState() 8212 currRev := fbo.getLatestMergedRevision(lState) 8213 8214 fireNow := false 8215 if fbo.registerForUpdatesShouldFireNow() { 8216 ctx = rpc.WithFireNow(ctx) 8217 fireNow = true 8218 } 8219 8220 startTime, timer := fbo.startOp( 8221 ctx, "Registering for updates (curr rev = %d, fire now = %v)", 8222 currRev, fireNow) 8223 defer func() { 8224 fbo.endOp( 8225 ctx, startTime, timer, 8226 "Registering for updates (curr rev = %d, fire now = %v) done: %+v", 8227 currRev, fireNow, err) 8228 }() 8229 // RegisterForUpdate will itself retry on connectivity issues 8230 return fbo.config.MDServer().RegisterForUpdate(ctx, fbo.id(), currRev) 8231 } 8232 8233 func (fbo *folderBranchOps) waitForAndProcessUpdates( 8234 ctx context.Context, lastUpdate time.Time, 8235 updateChan <-chan error) (currUpdate time.Time, err error) { 8236 // successful registration; now, wait for an update or a shutdown 8237 fbo.vlog.CLogf(ctx, libkb.VLog1, "Waiting for updates") 8238 defer func() { 8239 fbo.deferLogIfErr(ctx, err, "Waiting for updates done: %+v", err) 8240 }() 8241 8242 lState := makeFBOLockState() 8243 8244 for { 8245 select { 8246 case err := <-updateChan: 8247 fbo.vlog.CLogf(ctx, libkb.VLog1, "Got an update: %v", err) 8248 if err != nil { 8249 return time.Time{}, err 8250 } 8251 // Getting and applying the updates requires holding 8252 // locks, so make sure it doesn't take too long. 8253 ctx, cancel := context.WithTimeout(ctx, data.BackgroundTaskTimeout) 8254 defer cancel() 8255 8256 currUpdate := fbo.config.Clock().Now() 8257 ffDone, err := 8258 fbo.maybeFastForward(ctx, lState, lastUpdate, currUpdate) 8259 if err != nil { 8260 return time.Time{}, err 8261 } 8262 if ffDone { 8263 return currUpdate, nil 8264 } 8265 8266 err = fbo.getAndApplyMDUpdates(ctx, lState, nil, fbo.applyMDUpdates) 8267 if err != nil { 8268 fbo.log.CDebugf(ctx, "Got an error while applying "+ 8269 "updates: %v", err) 8270 return time.Time{}, err 8271 } 8272 return currUpdate, nil 8273 case unpause := <-fbo.updatePauseChan: 8274 fbo.log.CInfof(ctx, "Updates paused") 8275 // wait to be unpaused 8276 select { 8277 case <-unpause: 8278 fbo.log.CInfof(ctx, "Updates unpaused") 8279 case <-ctx.Done(): 8280 return time.Time{}, ctx.Err() 8281 } 8282 case <-ctx.Done(): 8283 return time.Time{}, ctx.Err() 8284 } 8285 } 8286 } 8287 8288 func (fbo *folderBranchOps) getCachedDirOpsCount( 8289 lState *kbfssync.LockState) int { 8290 fbo.mdWriterLock.Lock(lState) 8291 defer fbo.mdWriterLock.Unlock(lState) 8292 return len(fbo.dirOps) 8293 } 8294 8295 func (fbo *folderBranchOps) backgroundFlusher() { 8296 lState := makeFBOLockState() 8297 var prevDirtyFileMap map[data.BlockRef]bool 8298 sameDirtyFileCount := 0 8299 for { 8300 doSelect := true 8301 if fbo.blocks.GetState(lState) == dirtyState && 8302 fbo.config.DirtyBlockCache().ShouldForceSync(fbo.id()) && 8303 sameDirtyFileCount < 10 { 8304 // We have dirty files, and the system has a full buffer, 8305 // so don't bother waiting for a signal, just get right to 8306 // the main attraction. 8307 doSelect = false 8308 } else if fbo.getCachedDirOpsCount(lState) >= 8309 fbo.config.BGFlushDirOpBatchSize() { 8310 doSelect = false 8311 } 8312 8313 if doSelect { 8314 // Wait until we really have a write waiting. 8315 doWait := true 8316 select { 8317 case <-fbo.syncNeededChan: 8318 if fbo.getCachedDirOpsCount(lState) >= 8319 fbo.config.BGFlushDirOpBatchSize() { 8320 doWait = false 8321 } 8322 case <-fbo.forceSyncChan: 8323 doWait = false 8324 case <-fbo.shutdownChan: 8325 return 8326 } 8327 8328 if doWait { 8329 timer := time.NewTimer(fbo.config.BGFlushPeriod()) 8330 // Loop until either a tick's worth of time passes, 8331 // the batch size of directory ops is full, a sync is 8332 // forced, or a shutdown happens. 8333 loop: 8334 for { 8335 select { 8336 case <-timer.C: 8337 break loop 8338 case <-fbo.syncNeededChan: 8339 if fbo.getCachedDirOpsCount(lState) >= 8340 fbo.config.BGFlushDirOpBatchSize() { 8341 break loop 8342 } 8343 case <-fbo.forceSyncChan: 8344 break loop 8345 case <-fbo.shutdownChan: 8346 return 8347 } 8348 } 8349 } 8350 } 8351 8352 dirtyFiles := fbo.blocks.GetDirtyFileBlockRefs(lState) 8353 dirOpsCount := fbo.getCachedDirOpsCount(lState) 8354 if len(dirtyFiles) == 0 && dirOpsCount == 0 { 8355 sameDirtyFileCount = 0 8356 continue 8357 } 8358 8359 // Make sure we are making some progress 8360 currDirtyFileMap := make(map[data.BlockRef]bool) 8361 for _, ref := range dirtyFiles { 8362 currDirtyFileMap[ref] = true 8363 } 8364 if reflect.DeepEqual(currDirtyFileMap, prevDirtyFileMap) { 8365 sameDirtyFileCount++ 8366 } else { 8367 sameDirtyFileCount = 0 8368 } 8369 prevDirtyFileMap = currDirtyFileMap 8370 8371 _ = fbo.runUnlessShutdown(func(ctx context.Context) (err error) { 8372 // Denote that these are coming from a background 8373 // goroutine, not directly from any user. 8374 ctx = libcontext.NewContextReplayable(ctx, 8375 func(ctx context.Context) context.Context { 8376 return context.WithValue(ctx, CtxBackgroundSyncKey, "1") 8377 }) 8378 8379 fbo.vlog.CLogf( 8380 ctx, libkb.VLog1, "Background sync triggered: %d dirty files, "+ 8381 "%d dir ops in batch", len(dirtyFiles), dirOpsCount) 8382 8383 if sameDirtyFileCount >= 100 { 8384 // If the local journal is full, we might not be able to 8385 // make progress until more data is flushed to the 8386 // servers, so just warn here rather than just an outright 8387 // panic. 8388 fbo.log.CWarningf(ctx, "Making no Sync progress on dirty "+ 8389 "files after %d attempts: %v", sameDirtyFileCount, 8390 dirtyFiles) 8391 } 8392 8393 // Just in case network access or a bug gets stuck for a 8394 // long time, time out the sync eventually. 8395 longCtx, longCancel := 8396 context.WithTimeout(ctx, data.BackgroundTaskTimeout) 8397 defer longCancel() 8398 err = fbo.SyncAll(longCtx, fbo.folderBranch) 8399 if err != nil { 8400 // Just log the warning and keep trying to 8401 // sync the rest of the dirty files. 8402 fbo.log.CWarningf(ctx, "Couldn't sync all: %+v", err) 8403 } 8404 return nil 8405 }) 8406 } 8407 } 8408 8409 func (fbo *folderBranchOps) blockUnmergedWrites(lState *kbfssync.LockState) { 8410 fbo.mdWriterLock.Lock(lState) 8411 } 8412 8413 func (fbo *folderBranchOps) unblockUnmergedWrites(lState *kbfssync.LockState) { 8414 fbo.mdWriterLock.Unlock(lState) 8415 } 8416 8417 func (fbo *folderBranchOps) finalizeResolutionLocked(ctx context.Context, 8418 lState *kbfssync.LockState, md *RootMetadata, bps blockPutState, 8419 newOps []op, blocksToDelete []kbfsblock.ID) error { 8420 fbo.mdWriterLock.AssertLocked(lState) 8421 8422 // Put the blocks into the cache so that, even if we fail below, 8423 // future attempts may reuse the blocks. 8424 err := fbo.finalizeBlocks(ctx, bps) 8425 if err != nil { 8426 return err 8427 } 8428 8429 // Last chance to get pre-empted. 8430 select { 8431 case <-ctx.Done(): 8432 return ctx.Err() 8433 default: 8434 } 8435 8436 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 8437 if err != nil { 8438 return err 8439 } 8440 irmd, err := fbo.config.MDOps().ResolveBranch( 8441 ctx, fbo.id(), fbo.unmergedBID, blocksToDelete, md, 8442 session.VerifyingKey, bps) 8443 md = irmd.ReadOnlyRootMetadata.RootMetadata // un-read-onlyify 8444 doUnmergedPut := isRevisionConflict(err) 8445 if doUnmergedPut { 8446 fbo.log.CDebugf(ctx, "Got a conflict after resolution; aborting CR") 8447 return err 8448 } 8449 if err != nil { 8450 return err 8451 } 8452 8453 // Queue a rekey if the bit was set. 8454 if md.IsRekeySet() { 8455 defer fbo.config.RekeyQueue().Enqueue(md.TlfID()) 8456 } 8457 8458 // Set the head to the new MD. 8459 fbo.headLock.Lock(lState) 8460 defer fbo.headLock.Unlock(lState) 8461 err = fbo.setHeadConflictResolvedLocked(ctx, lState, irmd) 8462 if err != nil { 8463 fbo.log.CWarningf(ctx, "Couldn't set local MD head after a "+ 8464 "successful put: %v", err) 8465 return err 8466 } 8467 fbo.setBranchIDLocked(lState, kbfsmd.NullBranchID) 8468 8469 if TLFJournalEnabled(fbo.config, fbo.id()) { 8470 // Send unflushed notifications if journaling is on. 8471 err := fbo.handleUnflushedEditNotifications(ctx, irmd) 8472 if err != nil { 8473 fbo.log.CWarningf(ctx, "Couldn't send unflushed edit "+ 8474 "notifications for revision %d: %+v", irmd.Revision(), err) 8475 } 8476 } else { 8477 // Send edit notifications and archive the old, unref'd blocks 8478 // if journaling is off. 8479 fbo.editActivity.Add(1) 8480 fbo.goTracked(func() { 8481 defer fbo.editActivity.Done() 8482 ctx, cancelFunc := fbo.newCtxWithFBOID() 8483 defer cancelFunc() 8484 err := fbo.handleEditNotifications(ctx, irmd) 8485 if err != nil { 8486 fbo.log.CWarningf(ctx, "Couldn't send edit notifications for "+ 8487 "revision %d: %+v", irmd.Revision(), err) 8488 } 8489 }) 8490 fbo.fbm.archiveUnrefBlocks(irmd.ReadOnly()) 8491 } 8492 8493 mdCopyWithLocalOps, err := md.deepCopy(fbo.config.Codec()) 8494 if err != nil { 8495 return err 8496 } 8497 mdCopyWithLocalOps.data.Changes.Ops = newOps 8498 8499 // notifyOneOp for every fixed-up merged op. 8500 for _, op := range newOps { 8501 err := fbo.notifyOneOpLocked( 8502 ctx, lState, op, mdCopyWithLocalOps.ReadOnly(), false) 8503 if err != nil { 8504 return err 8505 } 8506 } 8507 return nil 8508 } 8509 8510 // finalizeResolution caches all the blocks, and writes the new MD to 8511 // the merged branch, failing if there is a conflict. It also sends 8512 // out the given newOps notifications locally. This is used for 8513 // completing conflict resolution. 8514 func (fbo *folderBranchOps) finalizeResolution(ctx context.Context, 8515 lState *kbfssync.LockState, md *RootMetadata, bps blockPutState, 8516 newOps []op, blocksToDelete []kbfsblock.ID) error { 8517 // Take the writer lock. 8518 fbo.mdWriterLock.Lock(lState) 8519 defer fbo.mdWriterLock.Unlock(lState) 8520 return fbo.finalizeResolutionLocked( 8521 ctx, lState, md, bps, newOps, blocksToDelete) 8522 } 8523 8524 func (fbo *folderBranchOps) handleTLFBranchChange(ctx context.Context, 8525 newBID kbfsmd.BranchID) { 8526 lState := makeFBOLockState() 8527 fbo.mdWriterLock.Lock(lState) 8528 defer fbo.mdWriterLock.Unlock(lState) 8529 8530 fbo.log.CDebugf(ctx, "Journal branch change: %s", newBID) 8531 8532 if fbo.isUnmergedLocked(lState) { 8533 if fbo.unmergedBID == newBID { 8534 fbo.vlog.CLogf(ctx, libkb.VLog1, "Already on branch %s", newBID) 8535 return 8536 } 8537 panic(fmt.Sprintf("Cannot switch to branch %s while on branch %s", 8538 newBID, fbo.unmergedBID)) 8539 } 8540 8541 md, err := fbo.config.MDOps().GetUnmergedForTLF(ctx, fbo.id(), newBID) 8542 if err != nil { 8543 fbo.log.CWarningf(ctx, 8544 "No unmerged head on journal branch change (bid=%s)", newBID) 8545 return 8546 } 8547 8548 if md == (ImmutableRootMetadata{}) || md.MergedStatus() != kbfsmd.Unmerged || 8549 md.BID() != newBID { 8550 // This can happen if CR got kicked off in some other way and 8551 // completed before we took the lock to process this 8552 // notification. 8553 fbo.vlog.CLogf( 8554 ctx, libkb.VLog1, "Ignoring stale branch change: md=%v, newBID=%d", 8555 md, newBID) 8556 return 8557 } 8558 8559 // Everything we thought we knew about quota reclamation is now 8560 // called into question. 8561 fbo.fbm.clearLastQRData() 8562 8563 // Kick off conflict resolution and set the head to the correct branch. 8564 fbo.setBranchIDLocked(lState, newBID) 8565 fbo.cr.Resolve(ctx, md.Revision(), kbfsmd.RevisionUninitialized) 8566 8567 // Fixup the edit history unflushed state. 8568 fbo.editHistory.ClearAllUnflushed() 8569 err = fbo.handleUnflushedEditNotifications(ctx, md) 8570 if err != nil { 8571 fbo.log.CWarningf(ctx, "Couldn't send unflushed edit "+ 8572 "notifications for revision %d: %+v", md.Revision(), err) 8573 } 8574 8575 fbo.headLock.Lock(lState) 8576 defer fbo.headLock.Unlock(lState) 8577 err = fbo.setHeadSuccessorLocked(ctx, lState, md, true /*rebased*/) 8578 if err != nil { 8579 fbo.log.CWarningf(ctx, 8580 "Could not set head on journal branch change: %v", err) 8581 return 8582 } 8583 } 8584 8585 func (fbo *folderBranchOps) onTLFBranchChange(newBID kbfsmd.BranchID) { 8586 fbo.branchChanges.Add(1) 8587 8588 fbo.goTracked(func() { 8589 defer fbo.branchChanges.Done() 8590 ctx, cancelFunc := fbo.newCtxWithFBOID() 8591 defer cancelFunc() 8592 8593 // This only happens on a `PruneBranch` call, in which case we 8594 // would have already updated fbo's local view of the branch/head. 8595 if newBID == kbfsmd.NullBranchID { 8596 fbo.vlog.CLogf( 8597 ctx, libkb.VLog1, "Ignoring branch change back to master") 8598 return 8599 } 8600 8601 fbo.handleTLFBranchChange(ctx, newBID) 8602 }) 8603 } 8604 8605 func (fbo *folderBranchOps) handleMDFlush( 8606 ctx context.Context, rev kbfsmd.Revision) { 8607 fbo.vlog.CLogf( 8608 ctx, libkb.VLog1, 8609 "Considering archiving references for flushed MD revision %d", rev) 8610 8611 lState := makeFBOLockState() 8612 var latestMergedUpdated <-chan struct{} 8613 func() { 8614 fbo.headLock.Lock(lState) 8615 defer fbo.headLock.Unlock(lState) 8616 fbo.setLatestMergedRevisionLocked(ctx, lState, rev, false) 8617 latestMergedUpdated = fbo.latestMergedUpdated 8618 }() 8619 8620 // Get that revision. 8621 rmd, err := GetSingleMD(ctx, fbo.config, fbo.id(), kbfsmd.NullBranchID, 8622 rev, kbfsmd.Merged, nil) 8623 if err != nil { 8624 fbo.log.CWarningf(ctx, "Couldn't get revision %d for archiving: %v", 8625 rev, err) 8626 return 8627 } 8628 8629 rmd, err = reembedBlockChangesIntoCopyIfNeeded( 8630 ctx, fbo.config.Codec(), fbo.config.BlockCache(), 8631 fbo.config.BlockOps(), fbo.config.Mode(), rmd, fbo.log) 8632 if err != nil { 8633 fbo.log.CWarningf(ctx, "Couldn't reembed revision %d: %v", 8634 rev, err) 8635 return 8636 } 8637 8638 err = fbo.handleEditNotifications(ctx, rmd) 8639 if err != nil { 8640 fbo.log.CWarningf(ctx, "Couldn't send edit notifications for "+ 8641 "revision %d: %+v", rev, err) 8642 } 8643 8644 fbo.editHistory.FlushRevision(rev) 8645 session, err := idutil.GetCurrentSessionIfPossible( 8646 ctx, fbo.config.KBPKI(), true) 8647 if err != nil { 8648 fbo.log.CWarningf(ctx, "Error getting session: %+v", err) 8649 } 8650 tlfName := rmd.GetTlfHandle().GetCanonicalName() 8651 fbo.config.UserHistory().UpdateHistory( 8652 tlfName, fbo.id().Type(), fbo.editHistory, string(session.Name)) 8653 8654 if err := isArchivableMDOrError(rmd.ReadOnly()); err != nil { 8655 fbo.log.CDebugf( 8656 ctx, "Skipping archiving references for flushed MD revision %d: %s", rev, err) 8657 return 8658 } 8659 fbo.fbm.archiveUnrefBlocks(rmd.ReadOnly()) 8660 8661 fbo.goTracked(func() { fbo.commitFlushedMD(rmd, latestMergedUpdated) }) 8662 } 8663 8664 func (fbo *folderBranchOps) onMDFlush( 8665 unmergedBID kbfsmd.BranchID, rev kbfsmd.Revision) { 8666 fbo.mdFlushes.Add(1) 8667 8668 fbo.goTracked(func() { 8669 defer fbo.mdFlushes.Done() 8670 ctx, cancelFunc := fbo.newCtxWithFBOID() 8671 defer cancelFunc() 8672 8673 if unmergedBID != kbfsmd.NullBranchID { 8674 fbo.vlog.CLogf( 8675 ctx, libkb.VLog1, "Ignoring MD flush on branch %v for "+ 8676 "revision %d", unmergedBID, rev) 8677 return 8678 } 8679 8680 fbo.handleMDFlush(ctx, rev) 8681 }) 8682 } 8683 8684 // TeamNameChanged implements the KBFSOps interface for folderBranchOps 8685 func (fbo *folderBranchOps) TeamNameChanged( 8686 ctx context.Context, tid keybase1.TeamID) { 8687 ctx, cancelFunc := fbo.newCtxWithFBOIDWithCtx(ctx) 8688 defer cancelFunc() 8689 8690 fbo.vlog.CLogf(ctx, libkb.VLog1, "Starting name change for team %s", tid) 8691 8692 // First check if this is an implicit team. 8693 var newName kbname.NormalizedUsername 8694 if fbo.id().Type() != tlf.SingleTeam { 8695 iteamInfo, err := fbo.config.KBPKI().ResolveImplicitTeamByID( 8696 ctx, tid, fbo.id().Type(), fbo.oa()) 8697 if err == nil { 8698 newName = iteamInfo.Name 8699 } 8700 } 8701 8702 if newName == "" { 8703 var err error 8704 newName, err = fbo.config.KBPKI().GetNormalizedUsername( 8705 ctx, tid.AsUserOrTeam(), fbo.oa()) 8706 if err != nil { 8707 fbo.log.CWarningf(ctx, "Error getting new team name: %+v", err) 8708 return 8709 } 8710 } 8711 8712 lState := makeFBOLockState() 8713 fbo.mdWriterLock.Lock(lState) 8714 defer fbo.mdWriterLock.Unlock(lState) 8715 fbo.headLock.Lock(lState) 8716 defer fbo.headLock.Unlock(lState) 8717 8718 if fbo.head == (ImmutableRootMetadata{}) { 8719 fbo.log.CWarningf(ctx, "No head to update") 8720 return 8721 } 8722 8723 oldHandle := fbo.head.GetTlfHandle() 8724 8725 if string(oldHandle.GetCanonicalName()) == string(newName) { 8726 fbo.vlog.CLogf( 8727 ctx, libkb.VLog1, "Name didn't change: %s", newName) 8728 return 8729 } 8730 8731 if oldHandle.FirstResolvedWriter() != tid.AsUserOrTeam() { 8732 fbo.log.CWarningf(ctx, 8733 "Old handle doesn't include changed team ID: %s", 8734 oldHandle.FirstResolvedWriter()) 8735 return 8736 } 8737 8738 // Make a copy of `head` with the new handle. 8739 newHandle := oldHandle.DeepCopy() 8740 newHandle.SetName(tlf.CanonicalName(newName)) 8741 newHandle.SetResolvedWriter(tid.AsUserOrTeam(), newName) 8742 newHead, err := fbo.head.deepCopy(fbo.config.Codec()) 8743 if err != nil { 8744 fbo.log.CWarningf(ctx, "Error copying head: %+v", err) 8745 return 8746 } 8747 newHead.tlfHandle = newHandle 8748 8749 fbo.log.CDebugf(ctx, "Team name changed from %s to %s", 8750 oldHandle.GetCanonicalName(), newHandle.GetCanonicalName()) 8751 fbo.head = MakeImmutableRootMetadata( 8752 newHead, fbo.head.lastWriterVerifyingKey, fbo.head.mdID, 8753 fbo.head.localTimestamp, fbo.head.putToServer) 8754 8755 fbo.config.MDCache().ChangeHandleForID(oldHandle, newHandle) 8756 fbo.observers.tlfHandleChange(ctx, newHandle) 8757 } 8758 8759 // TeamAbandoned implements the KBFSOps interface for folderBranchOps. 8760 func (fbo *folderBranchOps) TeamAbandoned( 8761 ctx context.Context, tid keybase1.TeamID) { 8762 ctx, cancelFunc := fbo.newCtxWithFBOIDWithCtx(ctx) 8763 defer cancelFunc() 8764 fbo.log.CDebugf(ctx, "Abandoning team %s", tid) 8765 fbo.locallyFinalizeTLF(ctx) 8766 } 8767 8768 func (fbo *folderBranchOps) getMDForMigrationLocked( 8769 ctx context.Context, lState *kbfssync.LockState) ( 8770 ImmutableRootMetadata, error) { 8771 fbo.mdWriterLock.AssertLocked(lState) 8772 8773 md, err := fbo.getMDForWriteOrRekeyLocked(ctx, lState, mdRekey) 8774 if err != nil { 8775 return ImmutableRootMetadata{}, err 8776 } 8777 8778 // Only writers may migrate TLFs. 8779 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 8780 if err != nil { 8781 return ImmutableRootMetadata{}, err 8782 } 8783 isWriter, err := md.IsWriter( 8784 ctx, fbo.config.KBPKI(), fbo.config, session.UID, session.VerifyingKey) 8785 if err != nil { 8786 return ImmutableRootMetadata{}, err 8787 } 8788 if !isWriter { 8789 return ImmutableRootMetadata{}, tlfhandle.NewWriteAccessError( 8790 md.GetTlfHandle(), session.Name, "") 8791 } 8792 8793 return md, nil 8794 } 8795 8796 // CheckMigrationPerms implements the KBFSOps interface for folderBranchOps. 8797 func (fbo *folderBranchOps) CheckMigrationPerms( 8798 ctx context.Context, id tlf.ID) (err error) { 8799 lState := makeFBOLockState() 8800 fbo.mdWriterLock.Lock(lState) 8801 defer fbo.mdWriterLock.Unlock(lState) 8802 8803 _, err = fbo.getMDForMigrationLocked(ctx, lState) 8804 return err 8805 } 8806 8807 // MigrateToImplicitTeam implements the KBFSOps interface for folderBranchOps. 8808 func (fbo *folderBranchOps) MigrateToImplicitTeam( 8809 ctx context.Context, id tlf.ID) (err error) { 8810 // Only MasterBranch FBOs may be migrated. 8811 fb := data.FolderBranch{Tlf: id, Branch: data.MasterBranch} 8812 if fb != fbo.folderBranch { 8813 // TODO: log instead of panic? 8814 panic(WrongOpsError{fbo.folderBranch, fb}) 8815 } 8816 8817 fbo.log.CDebugf(ctx, "Starting migration of TLF %s", id) 8818 defer func() { 8819 fbo.deferLog.CDebugf( 8820 ctx, "Finished migration of TLF %s, err=%+v", id, err) 8821 }() 8822 8823 if id.Type() != tlf.Private && id.Type() != tlf.Public { 8824 return errors.Errorf("Cannot migrate a TLF of type: %s", id.Type()) 8825 } 8826 8827 lState := makeFBOLockState() 8828 fbo.mdWriterLock.Lock(lState) 8829 defer fbo.mdWriterLock.Unlock(lState) 8830 8831 md, err := fbo.getMDForMigrationLocked(ctx, lState) 8832 if err != nil { 8833 return err 8834 } 8835 8836 if md == (ImmutableRootMetadata{}) { 8837 fbo.log.CDebugf(ctx, "Nothing to upgrade") 8838 return nil 8839 } 8840 8841 if md.IsFinal() { 8842 fbo.log.CDebugf(ctx, "No need to upgrade a finalized TLF") 8843 return nil 8844 } 8845 8846 if md.TypeForKeying() == tlf.TeamKeying { 8847 fbo.log.CDebugf(ctx, "Already migrated") 8848 return nil 8849 } 8850 8851 name := string(md.GetTlfHandle().GetCanonicalName()) 8852 fbo.log.CDebugf(ctx, "Looking up implicit team for %s", name) 8853 newHandle, err := tlfhandle.ParseHandle( 8854 ctx, fbo.config.KBPKI(), fbo.config.MDOps(), fbo.config, 8855 name, id.Type()) 8856 if err != nil { 8857 return err 8858 } 8859 8860 // Make sure the new handle contains just a team. 8861 if newHandle.TypeForKeying() != tlf.TeamKeying { 8862 return errors.New("No corresponding implicit team yet") 8863 } 8864 8865 session, err := fbo.config.KBPKI().GetCurrentSession(ctx) 8866 if err != nil { 8867 return err 8868 } 8869 8870 isWriter := true // getMDForMigrationLocked already checked this. 8871 newMD, err := md.MakeSuccessorWithNewHandle( 8872 ctx, newHandle, fbo.config.MetadataVersion(), fbo.config.Codec(), 8873 fbo.config.KeyManager(), fbo.config.KBPKI(), fbo.config.KBPKI(), 8874 fbo.config, md.mdID, isWriter) 8875 if err != nil { 8876 return err 8877 } 8878 8879 if newMD.TypeForKeying() != tlf.TeamKeying { 8880 return errors.New("Migration failed") 8881 } 8882 8883 // Add an empty operation to satisfy assumptions elsewhere. 8884 newMD.AddOp(newRekeyOp()) 8885 8886 return fbo.finalizeMDRekeyWriteLocked( 8887 ctx, lState, newMD, session.VerifyingKey) 8888 } 8889 8890 // GetUpdateHistory implements the KBFSOps interface for folderBranchOps 8891 func (fbo *folderBranchOps) GetUpdateHistory( 8892 ctx context.Context, folderBranch data.FolderBranch, 8893 start, end kbfsmd.Revision) (history TLFUpdateHistory, err error) { 8894 startTime, timer := fbo.startOp(ctx, "GetUpdateHistory(%d, %d)", start, end) 8895 defer func() { 8896 fbo.endOp( 8897 ctx, startTime, timer, "GetUpdateHistory(%d, %d) done: %+v", 8898 start, end, err) 8899 }() 8900 8901 if folderBranch != fbo.folderBranch { 8902 return TLFUpdateHistory{}, WrongOpsError{fbo.folderBranch, folderBranch} 8903 } 8904 8905 rmds, err := getMergedMDUpdatesWithEnd( 8906 ctx, fbo.config, fbo.id(), start, end, nil) 8907 if err != nil { 8908 return TLFUpdateHistory{}, err 8909 } 8910 8911 if len(rmds) > 0 { 8912 rmd := rmds[len(rmds)-1] 8913 history.ID = rmd.TlfID().String() 8914 history.Name = rmd.GetTlfHandle().GetCanonicalPath() 8915 } 8916 history.Updates = make([]UpdateSummary, 0, len(rmds)) 8917 writerNames := make(map[keybase1.UID]string) 8918 for _, rmd := range rmds { 8919 writer, ok := writerNames[rmd.LastModifyingWriter()] 8920 if !ok { 8921 name, err := fbo.config.KBPKI().GetNormalizedUsername( 8922 ctx, rmd.LastModifyingWriter().AsUserOrTeam(), fbo.oa()) 8923 if err != nil { 8924 return TLFUpdateHistory{}, err 8925 } 8926 writer = string(name) 8927 writerNames[rmd.LastModifyingWriter()] = writer 8928 } 8929 updateSummary := UpdateSummary{ 8930 Revision: rmd.Revision(), 8931 Date: rmd.localTimestamp, 8932 Writer: writer, 8933 LiveBytes: rmd.DiskUsage(), 8934 Ops: make([]OpSummary, 0, len(rmd.data.Changes.Ops)), 8935 RootBlockID: rmd.data.Dir.ID.String(), 8936 } 8937 for _, op := range rmd.data.Changes.Ops { 8938 opSummary := OpSummary{ 8939 Op: op.String(), 8940 Refs: make([]string, 0, len(op.Refs())), 8941 Unrefs: make([]string, 0, len(op.Unrefs())), 8942 Updates: make(map[string]string), 8943 } 8944 for _, ptr := range op.Refs() { 8945 opSummary.Refs = append(opSummary.Refs, ptr.String()) 8946 } 8947 for _, ptr := range op.Unrefs() { 8948 opSummary.Unrefs = append(opSummary.Unrefs, ptr.String()) 8949 } 8950 for _, update := range op.allUpdates() { 8951 opSummary.Updates[update.Unref.String()] = update.Ref.String() 8952 } 8953 updateSummary.Ops = append(updateSummary.Ops, opSummary) 8954 } 8955 history.Updates = append(history.Updates, updateSummary) 8956 } 8957 return history, nil 8958 } 8959 8960 // GetEditHistory implements the KBFSOps interface for folderBranchOps 8961 func (fbo *folderBranchOps) GetEditHistory( 8962 ctx context.Context, _ data.FolderBranch) ( 8963 tlfHistory keybase1.FSFolderEditHistory, err error) { 8964 // Wait for any outstanding edit requests. 8965 if err := fbo.editActivity.Wait(ctx); err != nil { 8966 return keybase1.FSFolderEditHistory{}, err 8967 } 8968 8969 lState := makeFBOLockState() 8970 md, _ := fbo.getHead(ctx, lState, mdNoCommit) 8971 name := md.GetTlfHandle().GetCanonicalName() 8972 return fbo.config.UserHistory().GetTlfHistory(name, fbo.id().Type()), nil 8973 } 8974 8975 // PushStatusChange forces a new status be fetched by status listeners. 8976 func (fbo *folderBranchOps) PushStatusChange() { 8977 fbo.config.KBFSOps().PushStatusChange() 8978 } 8979 8980 // ClearPrivateFolderMD implements the KBFSOps interface for 8981 // folderBranchOps. 8982 func (fbo *folderBranchOps) ClearPrivateFolderMD(ctx context.Context) { 8983 func() { 8984 // Cancel the edits goroutine and forget the old history, even 8985 // for public folders, since some of the state in the history 8986 // is dependent on your login state. 8987 fbo.editsLock.Lock() 8988 defer fbo.editsLock.Unlock() 8989 if fbo.cancelEdits != nil { 8990 fbo.cancelEdits() 8991 fbo.cancelEdits = nil 8992 } 8993 fbo.editHistory = kbfsedits.NewTlfHistory() 8994 // Allow the edit monitor to be re-launched later whenever the 8995 // MD is set again. 8996 fbo.launchEditMonitor = sync.Once{} 8997 fbo.convLock.Lock() 8998 defer fbo.convLock.Unlock() 8999 fbo.convID = nil 9000 }() 9001 9002 lState := makeFBOLockState() 9003 fbo.mdWriterLock.Lock(lState) 9004 defer fbo.mdWriterLock.Unlock(lState) 9005 fbo.headLock.Lock(lState) 9006 defer fbo.headLock.Unlock(lState) 9007 9008 fbo.blocks.ClearChargedTo(lState) 9009 9010 if fbo.folderBranch.Tlf.Type() == tlf.Public { 9011 return 9012 } 9013 9014 if fbo.head == (ImmutableRootMetadata{}) { 9015 // Nothing to clear. 9016 return 9017 } 9018 9019 fbo.log.CDebugf(ctx, "Clearing folder MD") 9020 9021 // First cancel the background goroutine that's registered for 9022 // updates, because the next time we set the head in this FBO 9023 // we'll launch another one. 9024 fbo.cancelUpdatesLock.Lock() 9025 defer fbo.cancelUpdatesLock.Unlock() 9026 if fbo.cancelUpdates != nil { 9027 fbo.cancelUpdates() 9028 select { 9029 case <-fbo.updateDoneChan: 9030 case <-ctx.Done(): 9031 fbo.log.CDebugf( 9032 ctx, "Context canceled before updater was canceled") 9033 return 9034 } 9035 fbo.config.MDServer().CancelRegistration(ctx, fbo.id()) 9036 } 9037 9038 fbo.head = ImmutableRootMetadata{} 9039 fbo.headStatus = headUntrusted 9040 fbo.latestMergedRevision = kbfsmd.RevisionUninitialized 9041 fbo.hasBeenCleared = true 9042 9043 // Clear the log obfuscation secret as well, so it can be re-set 9044 // when the head is re-established. 9045 fbo.obLock.Lock() 9046 defer fbo.obLock.Unlock() 9047 fbo.obSecret = nil 9048 } 9049 9050 // ForceFastForward implements the KBFSOps interface for 9051 // folderBranchOps. 9052 func (fbo *folderBranchOps) ForceFastForward(ctx context.Context) { 9053 lState := makeFBOLockState() 9054 fbo.headLock.RLock(lState) 9055 defer fbo.headLock.RUnlock(lState) 9056 if fbo.head != (ImmutableRootMetadata{}) { 9057 // We're already up to date. 9058 return 9059 } 9060 if !fbo.hasBeenCleared { 9061 // No reason to fast-forward here if it hasn't ever been 9062 // cleared. 9063 return 9064 } 9065 9066 fbo.forcedFastForwards.Add(1) 9067 fbo.goTracked(func() { 9068 defer fbo.forcedFastForwards.Done() 9069 ctx, cancelFunc := fbo.newCtxWithFBOID() 9070 defer cancelFunc() 9071 9072 fbo.log.CDebugf(ctx, "Forcing a fast-forward") 9073 var currHead ImmutableRootMetadata 9074 var err error 9075 getMD: 9076 for i := 0; ; i++ { 9077 currHead, err = fbo.config.MDOps().GetForTLF(ctx, fbo.id(), nil) 9078 switch errors.Cause(err).(type) { 9079 case nil: 9080 break getMD 9081 case kbfsmd.ServerErrorUnauthorized: 9082 // The MD server connection might not be authorized 9083 // yet, so give it a few chances to go through. 9084 if i > 5 { 9085 fbo.log.CDebugf(ctx, 9086 "Still unauthorized for TLF %s; giving up fast-forward", 9087 fbo.id()) 9088 return 9089 } 9090 if i == 0 { 9091 fbo.log.CDebugf( 9092 ctx, "Got unauthorized error when fast-forwarding %s; "+ 9093 "trying again after a delay", fbo.id()) 9094 } 9095 time.Sleep(1 * time.Second) 9096 default: 9097 fbo.log.CDebugf(ctx, "Fast-forward failed: %+v", err) 9098 return 9099 } 9100 } 9101 if currHead == (ImmutableRootMetadata{}) { 9102 fbo.log.CDebugf(ctx, "No MD yet") 9103 return 9104 } 9105 fbo.log.CDebugf(ctx, "Current head is revision %d", currHead.Revision()) 9106 9107 lState := makeFBOLockState() 9108 // Kick off partial prefetching once the latest merged 9109 // revision is set. 9110 defer func() { 9111 if err == nil { 9112 fbo.kickOffPartialSyncIfNeeded(ctx, lState, currHead) 9113 } 9114 }() 9115 9116 fbo.mdWriterLock.Lock(lState) 9117 defer fbo.mdWriterLock.Unlock(lState) 9118 fbo.headLock.Lock(lState) 9119 defer fbo.headLock.Unlock(lState) 9120 9121 if !fbo.hasBeenCleared { 9122 return 9123 } 9124 9125 defer func() { 9126 if fbo.head != (ImmutableRootMetadata{}) { 9127 fbo.hasBeenCleared = false 9128 } 9129 }() 9130 9131 if fbo.head != (ImmutableRootMetadata{}) { 9132 // We're already up to date. 9133 fbo.log.CDebugf(ctx, "Already up-to-date: %v", err) 9134 return 9135 } 9136 9137 err = fbo.doFastForwardLocked(ctx, lState, currHead) 9138 if err != nil { 9139 fbo.log.CDebugf(ctx, "Fast-forward failed: %v", err) 9140 } 9141 }) 9142 } 9143 9144 func (fbo *folderBranchOps) invalidateAllNodesLocked( 9145 ctx context.Context, lState *kbfssync.LockState) error { 9146 fbo.mdWriterLock.AssertLocked(lState) 9147 fbo.headLock.AssertLocked(lState) 9148 9149 changes, affectedNodeIDs, err := fbo.blocks.GetInvalidationChangesForAll( 9150 ctx, lState) 9151 if err != nil { 9152 return err 9153 } 9154 9155 // Invalidate all the affected nodes. 9156 if len(changes) > 0 { 9157 fbo.observers.batchChanges(ctx, changes, affectedNodeIDs) 9158 } 9159 return nil 9160 } 9161 9162 func (fbo *folderBranchOps) invalidateAllNodes(ctx context.Context) error { 9163 lState := makeFBOLockState() 9164 fbo.mdWriterLock.Lock(lState) 9165 defer fbo.mdWriterLock.Unlock(lState) 9166 fbo.headLock.Lock(lState) 9167 defer fbo.headLock.Unlock(lState) 9168 9169 fbo.log.CDebugf(ctx, "Invalidating all nodes") 9170 return fbo.invalidateAllNodesLocked(ctx, lState) 9171 } 9172 9173 // Reset implements the KBFSOps interface for folderBranchOps. 9174 func (fbo *folderBranchOps) Reset( 9175 ctx context.Context, handle *tlfhandle.Handle) error { 9176 currHandle, err := fbo.GetTLFHandle(ctx, nil) 9177 if err != nil { 9178 // If the MD is completely unreadable from the server, we 9179 // might not have been able to initialize it at all, and we 9180 // still want to allow resets in that case. 9181 fbo.log.CDebugf(ctx, "Skipping handle check due to error: %+v", err) 9182 currHandle = nil 9183 } 9184 if currHandle != nil { 9185 equal, err := currHandle.Equals(fbo.config.Codec(), *handle) 9186 if err != nil { 9187 return err 9188 } 9189 if !equal { 9190 return errors.Errorf("Can't reset %#v given bad handle %#v", 9191 currHandle, handle) 9192 } 9193 } 9194 9195 oldHandle := handle.DeepCopy() 9196 9197 lState := makeFBOLockState() 9198 fbo.mdWriterLock.Lock(lState) 9199 defer fbo.mdWriterLock.Unlock(lState) 9200 fbo.headLock.Lock(lState) 9201 defer fbo.headLock.Unlock(lState) 9202 9203 fbo.log.CDebugf(ctx, "Resetting") 9204 err = fbo.invalidateAllNodesLocked(ctx, lState) 9205 if err != nil { 9206 return err 9207 } 9208 9209 // Make up a finalized name for the old handle, and broadcast it 9210 // to all observers. This is to move it out of the way of the 9211 // next iteration of the folder. 9212 now := fbo.config.Clock().Now() 9213 finalizedInfo, err := tlf.NewHandleExtension( 9214 tlf.HandleExtensionFinalized, 1, kbname.NormalizedUsername("<unknown>"), 9215 now) 9216 if err != nil { 9217 return err 9218 } 9219 oldHandle.SetFinalizedInfo(finalizedInfo) 9220 // FIXME: This can't be subject to the WaitGroup due to a potential 9221 // deadlock, so we use a raw goroutine here instead of `goTracked`. 9222 go fbo.observers.tlfHandleChange(ctx, oldHandle) 9223 return nil 9224 } 9225 9226 // GetSyncConfig implements the KBFSOps interface for folderBranchOps. 9227 func (fbo *folderBranchOps) GetSyncConfig( 9228 ctx context.Context, tlfID tlf.ID) (keybase1.FolderSyncConfig, error) { 9229 if tlfID != fbo.id() || fbo.branch() != data.MasterBranch { 9230 return keybase1.FolderSyncConfig{}, WrongOpsError{ 9231 fbo.folderBranch, data.FolderBranch{ 9232 Tlf: tlfID, 9233 Branch: data.MasterBranch, 9234 }} 9235 } 9236 9237 lState := makeFBOLockState() 9238 md, _ := fbo.getHead(ctx, lState, mdNoCommit) 9239 config, tlfPath, err := fbo.getProtocolSyncConfigUnlocked(ctx, lState, md) 9240 if errors.Cause(err) == errNeedMDForPartialSyncConfig { 9241 // This is a partially-synced TLF, so it should be initialized 9242 // automatically by KBFSOps; we just need to wait for the MD. 9243 var once sync.Once 9244 for md == (ImmutableRootMetadata{}) { 9245 once.Do(func() { 9246 fbo.log.CDebugf( 9247 ctx, "Waiting for head to be populated while getting "+ 9248 "sync config") 9249 }) 9250 t := time.After(100 * time.Millisecond) 9251 select { 9252 case <-t: 9253 case <-ctx.Done(): 9254 return keybase1.FolderSyncConfig{}, errors.WithStack(ctx.Err()) 9255 } 9256 md, _ = fbo.getHead(ctx, lState, mdNoCommit) 9257 } 9258 config, tlfPath, err = fbo.getProtocolSyncConfigUnlocked( 9259 ctx, lState, md) 9260 } 9261 if err != nil { 9262 return keybase1.FolderSyncConfig{}, err 9263 } 9264 9265 if config.Mode == keybase1.FolderSyncMode_DISABLED || 9266 md == (ImmutableRootMetadata{}) || 9267 md.GetTlfHandle().GetCanonicalPath() == tlfPath { 9268 return config, nil 9269 } 9270 9271 // This means either the config was originally written before we 9272 // started saving TLF paths, or the TLF paths has changed due to 9273 // an SBS resolution or a subteam rename. Calling `SetSyncConfig` 9274 // will use the newest path from the MD's TlfHandle. 9275 fbo.log.CDebugf(ctx, "Updating sync config TLF path from \"%s\" to \"%s\"", 9276 tlfPath, md.GetTlfHandle().GetCanonicalPath()) 9277 _, err = fbo.SetSyncConfig(ctx, tlfID, config) 9278 if err != nil { 9279 fbo.log.CWarningf(ctx, "Couldn't update TLF path: %+v", err) 9280 } 9281 return config, nil 9282 } 9283 9284 func (fbo *folderBranchOps) makeEncryptedPartialPathsLocked( 9285 ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata, 9286 paths []string) (FolderSyncEncryptedPartialPaths, error) { 9287 fbo.syncLock.AssertLocked(lState) 9288 9289 oldConfig, _, err := fbo.getProtocolSyncConfig(ctx, lState, kmd) 9290 if err != nil { 9291 return FolderSyncEncryptedPartialPaths{}, err 9292 } 9293 if oldConfig.Mode == keybase1.FolderSyncMode_ENABLED { 9294 return FolderSyncEncryptedPartialPaths{}, 9295 errors.Errorf("TLF %s is already fully synced", fbo.id()) 9296 } 9297 9298 // Make sure the new path list doesn't contain duplicates, 9299 // contains no absolute paths, and each path is cleaned. 9300 seenPaths := make(map[string]bool, len(paths)) 9301 var pathList syncPathList 9302 pathList.Paths = make([]string, len(paths)) 9303 for i, p := range paths { 9304 p = stdpath.Clean(filepath.ToSlash(p)) 9305 if seenPaths[p] { 9306 return FolderSyncEncryptedPartialPaths{}, errors.Errorf( 9307 "%s is in the paths list more than once", p) 9308 } 9309 if stdpath.IsAbs(p) { 9310 return FolderSyncEncryptedPartialPaths{}, errors.Errorf( 9311 "Absolute paths like %s are not allowed", p) 9312 } 9313 if strings.HasPrefix(p, "..") { 9314 return FolderSyncEncryptedPartialPaths{}, errors.Errorf( 9315 "Relative paths out of the TLF like %s are not allowed", p) 9316 } 9317 seenPaths[p] = true 9318 pathList.Paths[i] = p 9319 } 9320 9321 fbo.log.CDebugf(ctx, 9322 "Setting partial sync config for %s; paths=%v", 9323 fbo.id(), pathList.Paths) 9324 9325 // Place the config data in a block that will be stored locally on 9326 // this device. It is not subject to the usual block size 9327 // limitations, and will not be sent to the bserver. 9328 b, err := pathList.makeBlock(fbo.config.Codec()) 9329 if err != nil { 9330 return FolderSyncEncryptedPartialPaths{}, err 9331 } 9332 9333 chargedTo, err := chargedToForTLF( 9334 ctx, fbo.config.KBPKI(), fbo.config.KBPKI(), fbo.config, 9335 kmd.GetTlfHandle()) 9336 if err != nil { 9337 return FolderSyncEncryptedPartialPaths{}, err 9338 } 9339 9340 info, _, readyBlockData, err := 9341 data.ReadyBlock(ctx, fbo.config.BlockCache(), fbo.config.BlockOps(), 9342 kmd, b, chargedTo, fbo.config.DefaultBlockType(), 9343 fbo.cacheHashBehavior()) 9344 if err != nil { 9345 return FolderSyncEncryptedPartialPaths{}, err 9346 } 9347 9348 // Put the unencrypted block in the cache. 9349 err = fbo.config.BlockCache().Put( 9350 info.BlockPointer, fbo.id(), b, data.TransientEntry, 9351 fbo.cacheHashBehavior()) 9352 if err != nil { 9353 fbo.log.CDebugf(ctx, 9354 "Error caching new block %v: %+v", info.BlockPointer, err) 9355 } 9356 9357 return FolderSyncEncryptedPartialPaths{ 9358 Ptr: info.BlockPointer, 9359 Buf: readyBlockData.Buf, 9360 ServerHalf: readyBlockData.ServerHalf, 9361 }, nil 9362 } 9363 9364 func (fbo *folderBranchOps) triggerMarkAndSweepLocked() { 9365 fbo.partialSyncs.Add(1) 9366 select { 9367 case fbo.markAndSweepTrigger <- struct{}{}: 9368 default: 9369 fbo.partialSyncs.Done() 9370 } 9371 } 9372 9373 func (fbo *folderBranchOps) reResolveAndIdentify( 9374 ctx context.Context, oldHandle *tlfhandle.Handle, rev kbfsmd.Revision) { 9375 fbo.log.CDebugf(ctx, "Re-resolving handle") 9376 defer func() { fbo.log.CDebugf(ctx, "Done") }() 9377 9378 fbo.config.KeybaseService().ClearCaches(ctx) 9379 9380 // Once with the iteam-resolver (to cause all the iteam data to be 9381 // cached in the service), and once without it (to cause all the 9382 // individual users of the folder to be cached in the service). 9383 tlfName := string(oldHandle.GetCanonicalName()) 9384 h, err := tlfhandle.ParseHandle( 9385 ctx, fbo.config.KBPKI(), fbo.config.MDOps(), fbo.config, 9386 tlfName, fbo.id().Type()) 9387 if err != nil { 9388 fbo.log.CDebugf(ctx, "Couldn't parse handle: %+v", err) 9389 return 9390 } 9391 9392 outer: 9393 for { 9394 _, err := tlfhandle.ParseHandlePreferredQuick( 9395 ctx, fbo.config.KBPKI(), fbo.config, tlfName, fbo.id().Type()) 9396 switch e := errors.Cause(err).(type) { 9397 case idutil.TlfNameNotCanonical: 9398 tlfName = e.NameToTry 9399 case nil: 9400 break outer 9401 default: 9402 fbo.log.CDebugf(ctx, "Couldn't parse handle: %+v", err) 9403 return 9404 } 9405 } 9406 9407 // Also re-download the MD, to make sure the team info is cached, 9408 // as well as the writer's keys. 9409 _, err = fbo.config.MDOps().GetRange(ctx, fbo.id(), rev, rev, nil) 9410 if err != nil { 9411 fbo.log.CDebugf(ctx, "Couldn't fetch MD revision %d: %+v", rev, err) 9412 return 9413 } 9414 9415 // Suppress tracker popups. 9416 ctx, err = tlfhandle.MakeExtendedIdentify( 9417 ctx, keybase1.TLFIdentifyBehavior_KBFS_INIT) 9418 if err != nil { 9419 fbo.log.CDebugf( 9420 ctx, "Couldn't make extended identify: %+v", err) 9421 return 9422 } 9423 9424 err = tlfhandle.IdentifyHandle( 9425 ctx, fbo.config.KBPKI(), fbo.config.KBPKI(), fbo.config, h) 9426 if err != nil { 9427 fbo.log.CDebugf(ctx, "Couldn't identify handle: %+v", err) 9428 } 9429 9430 // The popups and errors were suppressed, but any errors would 9431 // have been logged. So just close out the extended identify. If 9432 // the user accesses the TLF directly, another proper identify 9433 // should happen that shows errors. 9434 _ = tlfhandle.GetExtendedIdentify(ctx).GetTlfBreakAndClose() 9435 } 9436 9437 // SetSyncConfig implements the KBFSOps interface for KBFSOpsStandard. 9438 func (fbo *folderBranchOps) SetSyncConfig( 9439 ctx context.Context, tlfID tlf.ID, config keybase1.FolderSyncConfig) ( 9440 ch <-chan error, err error) { 9441 if tlfID != fbo.id() || fbo.branch() != data.MasterBranch { 9442 return nil, WrongOpsError{ 9443 fbo.folderBranch, data.FolderBranch{ 9444 Tlf: tlfID, 9445 Branch: data.MasterBranch, 9446 }} 9447 } 9448 9449 defer func() { 9450 if err == nil { 9451 fbo.config.SubscriptionManagerPublisher().PublishChange(keybase1.SubscriptionTopic_FAVORITES) 9452 fbo.config.Reporter().NotifyFavoritesChanged(ctx) 9453 } 9454 }() 9455 9456 lState := makeFBOLockState() 9457 md, err := fbo.getLatestMergedMD(ctx, lState) 9458 if err != nil { 9459 return nil, err 9460 } 9461 if md == (ImmutableRootMetadata{}) || 9462 md.Revision() == kbfsmd.RevisionUninitialized { 9463 return nil, errors.New( 9464 "Cannot set partial sync config on an uninitialized TLF") 9465 } 9466 9467 // Cancel any existing working set prefetches. 9468 if config.Mode != keybase1.FolderSyncMode_DISABLED { 9469 func() { 9470 fbo.headLock.Lock(lState) 9471 defer fbo.headLock.Unlock(lState) 9472 if fbo.latestMergedUpdated != nil { 9473 close(fbo.latestMergedUpdated) 9474 } 9475 fbo.latestMergedUpdated = make(chan struct{}) 9476 }() 9477 err = fbo.partialSyncs.Wait(ctx) 9478 if err != nil { 9479 return nil, err 9480 } 9481 } 9482 9483 // On the way back out (after the syncLock is released), kick off 9484 // the partial sync. 9485 defer func() { 9486 if err == nil && config.Mode == keybase1.FolderSyncMode_PARTIAL { 9487 fbo.kickOffPartialSync(ctx, lState, config, md) 9488 } 9489 }() 9490 9491 fbo.syncLock.Lock(lState) 9492 defer fbo.syncLock.Unlock(lState) 9493 9494 startTime, timer := fbo.startOp( 9495 ctx, "Setting sync config for %s, mode=%s", tlfID, config.Mode) 9496 defer func() { 9497 fbo.endOp( 9498 ctx, startTime, timer, 9499 "Done setting sync config for %s, mode=%s: %+v", 9500 tlfID, config.Mode, err) 9501 }() 9502 9503 if config.Mode == keybase1.FolderSyncMode_PARTIAL && 9504 len(config.Paths) == 0 { 9505 fbo.log.CDebugf(ctx, 9506 "Converting partial config with no paths into a disabled config") 9507 config.Mode = keybase1.FolderSyncMode_DISABLED 9508 } 9509 9510 newConfig := FolderSyncConfig{ 9511 Mode: config.Mode, 9512 TlfPath: md.GetTlfHandle().GetCanonicalPath(), 9513 } 9514 9515 if config.Mode == keybase1.FolderSyncMode_PARTIAL { 9516 paths, err := fbo.makeEncryptedPartialPathsLocked( 9517 ctx, lState, md, config.Paths) 9518 if err != nil { 9519 return nil, err 9520 } 9521 newConfig.Paths = paths 9522 } 9523 9524 oldConfig, _, err := fbo.getProtocolSyncConfig(ctx, lState, md) 9525 if err != nil { 9526 return nil, err 9527 } 9528 9529 defer func() { 9530 if err == nil && newConfig.Mode != oldConfig.Mode { 9531 fbo.config.GetPerfLog().CDebugf( 9532 ctx, "Set KBFS sync config for %s, new mode=%s, old mode=%s", 9533 tlfID, newConfig.Mode, oldConfig.Mode) 9534 } 9535 }() 9536 9537 oldPartial := oldConfig.Mode == keybase1.FolderSyncMode_PARTIAL 9538 newPartial := newConfig.Mode == keybase1.FolderSyncMode_PARTIAL 9539 switch { 9540 case oldPartial && !newPartial: 9541 if fbo.markAndSweepTrigger == nil { 9542 return nil, errors.New( 9543 "Unexpected sync config; mark-and-sweep already started") 9544 } 9545 9546 fbo.log.CDebugf(ctx, "Exiting partial mode, stopping mark-and-sweep") 9547 close(fbo.markAndSweepTrigger) 9548 fbo.markAndSweepTrigger = nil 9549 case !oldPartial && newPartial: 9550 if fbo.markAndSweepTrigger != nil { 9551 return nil, errors.New( 9552 "Unexpected sync config; mark-and-sweep already started") 9553 } 9554 if oldConfig.Mode == keybase1.FolderSyncMode_ENABLED { 9555 return nil, errors.New( 9556 "Cannot enable partial syncing while fully-synced") 9557 } 9558 9559 fbo.log.CDebugf(ctx, "Entering partial mode, starting mark-and-sweep") 9560 // `kickOffPartialSync` call above will start the mark and sweep. 9561 case oldPartial && newPartial: 9562 if fbo.markAndSweepTrigger == nil { 9563 return nil, errors.New( 9564 "Unexpected sync config; mark-and-sweep already started") 9565 } 9566 9567 // See if there are any missing paths from the new config. 9568 oldPaths := make(map[string]bool, len(oldConfig.Paths)) 9569 for _, p := range oldConfig.Paths { 9570 oldPaths[p] = true 9571 } 9572 for _, p := range config.Paths { 9573 delete(oldPaths, p) 9574 } 9575 if len(oldPaths) > 0 { 9576 for _, p := range oldPaths { 9577 fbo.log.CDebugf( 9578 ctx, "Path %s removed from partial config", p) 9579 } 9580 fbo.triggerMarkAndSweepLocked() 9581 } 9582 } 9583 9584 ch, err = fbo.config.SetTlfSyncState(ctx, tlfID, newConfig) 9585 if err != nil { 9586 return nil, err 9587 } 9588 9589 if oldConfig.Mode == keybase1.FolderSyncMode_DISABLED && 9590 newConfig.Mode != keybase1.FolderSyncMode_DISABLED { 9591 // Explicitly re-resolve and re-identify the handle, to make 9592 // sure the service caches everything it's supposed to. 9593 fbo.goTracked(func() { 9594 ctx, cancel := fbo.newCtxWithFBOID() 9595 defer cancel() 9596 fbo.reResolveAndIdentify(ctx, md.GetTlfHandle(), md.Revision()) 9597 }) 9598 } 9599 9600 modeChanged := oldConfig.Mode != config.Mode 9601 if config.Mode == keybase1.FolderSyncMode_ENABLED { 9602 // Make a new ctx for the root block fetch, since it will 9603 // continue after this function returns. 9604 rootBlockCtx := fbo.ctxWithFBOID(context.Background()) 9605 fbo.log.CDebugf( 9606 ctx, "Starting full deep sync with a new context: FBOID=%s", 9607 rootBlockCtx.Value(CtxFBOIDKey)) 9608 fbo.kickOffRootBlockFetchAndSyncInBackground(rootBlockCtx, md, nil) 9609 } else if modeChanged { 9610 fbo.syncedTlfObservers.syncModeChanged(ctx, fbo.id(), config.Mode) 9611 } 9612 9613 // Issue notifications to client when sync mode changes (or is partial). 9614 if modeChanged || config.Mode == keybase1.FolderSyncMode_PARTIAL { 9615 fbo.config.Reporter().Notify(ctx, syncConfigChangeNotification( 9616 md.GetTlfHandle(), config)) 9617 } 9618 return ch, nil 9619 } 9620 9621 // InvalidateNodeAndChildren implements the KBFSOps interface for 9622 // folderBranchOps. 9623 func (fbo *folderBranchOps) InvalidateNodeAndChildren( 9624 ctx context.Context, node Node) (err error) { 9625 startTime, timer := fbo.startOp(ctx, "InvalidateNodeAndChildren %p", node) 9626 defer func() { 9627 fbo.endOp( 9628 ctx, startTime, timer, "InvalidateNodeAndChildren %p done: %+v", 9629 node, err) 9630 }() 9631 9632 lState := makeFBOLockState() 9633 changes, affectedNodeIDs, err := fbo.blocks.GetInvalidationChangesForNode( 9634 ctx, lState, node) 9635 if err != nil { 9636 return err 9637 } 9638 9639 if len(changes) > 0 { 9640 fbo.observers.batchChanges(ctx, changes, affectedNodeIDs) 9641 } 9642 return nil 9643 } 9644 9645 func (fbo *folderBranchOps) getEditMonitoringChannel() <-chan struct{} { 9646 fbo.editsLock.Lock() 9647 defer fbo.editsLock.Unlock() 9648 return fbo.editMonitoringInProgress 9649 } 9650 9651 // NewNotificationChannel implements the KBFSOps interface for 9652 // folderBranchOps. 9653 func (fbo *folderBranchOps) NewNotificationChannel( 9654 ctx context.Context, handle *tlfhandle.Handle, convID chat1.ConversationID, 9655 channelName string) { 9656 monitoringCh := fbo.getEditMonitoringChannel() 9657 if monitoringCh == nil { 9658 fbo.vlog.CLogf( 9659 ctx, libkb.VLog1, 9660 "Ignoring new notification channel while edits are unmonitored") 9661 return 9662 } 9663 9664 fbo.vlog.CLogf( 9665 ctx, libkb.VLog1, "New notification channel: %s %s", 9666 convID, channelName) 9667 fbo.editActivity.Add(1) 9668 select { 9669 case fbo.editChannels <- editChannelActivity{convID, channelName, ""}: 9670 case <-monitoringCh: 9671 fbo.editActivity.Done() 9672 fbo.log.CDebugf(ctx, "Edit monitoring stopped while trying to "+ 9673 "send new notification channel") 9674 } 9675 } 9676 9677 // PushConnectionStatusChange pushes human readable connection status changes. 9678 func (fbo *folderBranchOps) PushConnectionStatusChange(service string, newStatus error) { 9679 switch service { 9680 case KeybaseServiceName, GregorServiceName: 9681 default: 9682 return 9683 } 9684 9685 if newStatus != nil { 9686 return 9687 } 9688 9689 monitoringCh := fbo.getEditMonitoringChannel() 9690 if monitoringCh == nil { 9691 return 9692 } 9693 9694 fbo.vlog.CLogf( 9695 context.TODO(), libkb.VLog1, 9696 "Asking for an edit re-init after reconnection") 9697 fbo.editActivity.Add(1) 9698 select { 9699 case fbo.editChannels <- editChannelActivity{nil, "", ""}: 9700 case <-monitoringCh: 9701 fbo.editActivity.Done() 9702 fbo.log.CDebugf( 9703 context.TODO(), 9704 "Edit monitoring stopped while trying to ask for a re-init") 9705 } 9706 } 9707 9708 func (fbo *folderBranchOps) receiveNewEditChat( 9709 convID chat1.ConversationID, message string) { 9710 monitoringCh := fbo.getEditMonitoringChannel() 9711 if monitoringCh == nil { 9712 return 9713 } 9714 9715 fbo.editActivity.Add(1) 9716 select { 9717 case fbo.editChannels <- editChannelActivity{convID, "", message}: 9718 case <-monitoringCh: 9719 fbo.editActivity.Done() 9720 } 9721 } 9722 9723 func (fbo *folderBranchOps) initEditChatChannels( 9724 ctx context.Context, name tlf.CanonicalName) ( 9725 idToName map[chat1.ConvIDStr]string, 9726 nameToID map[string]chat1.ConversationID, 9727 nameToNextPage map[string][]byte, err error) { 9728 convIDs, channelNames, err := fbo.config.Chat().GetChannels( 9729 ctx, name, fbo.id().Type(), chat1.TopicType_KBFSFILEEDIT) 9730 if err != nil { 9731 return nil, nil, nil, err 9732 } 9733 9734 idToName = make(map[chat1.ConvIDStr]string, len(convIDs)) 9735 nameToID = make(map[string]chat1.ConversationID, len(convIDs)) 9736 nameToNextPage = make(map[string][]byte, len(convIDs)) 9737 for i, id := range convIDs { 9738 fbo.config.Chat().RegisterForMessages(id, fbo.receiveNewEditChat) 9739 name := channelNames[i] 9740 idToName[id.ConvIDStr()] = name 9741 nameToID[name] = id 9742 nextPage := fbo.getEditMessages(ctx, id, name, nil) 9743 if nextPage != nil { 9744 nameToNextPage[name] = nextPage 9745 } 9746 } 9747 return idToName, nameToID, nameToNextPage, nil 9748 } 9749 9750 func (fbo *folderBranchOps) getEditMessages( 9751 ctx context.Context, id chat1.ConversationID, channelName string, 9752 startPage []byte) (nextPage []byte) { 9753 // TODO: be smarter about not fetching messages we've already 9754 // seen? `AddNotifications` below will filter out any duplicates, 9755 // so it's not strictly needed for correctness. 9756 messages, nextPage, err := fbo.config.Chat().ReadChannel(ctx, id, startPage) 9757 if err != nil { 9758 fbo.log.CWarningf(ctx, "Couldn't get messages for conv %s: %+v", 9759 id, err) 9760 return nil 9761 } 9762 if fbo.config.IsTestMode() { 9763 // Extra debugging for HOTPOT-1096. 9764 fbo.vlog.CLogf( 9765 ctx, libkb.VLog1, "%p: Got messages in channel %s: %s", 9766 fbo, channelName, messages) 9767 } 9768 9769 _, err = fbo.editHistory.AddNotifications(channelName, messages) 9770 if err != nil { 9771 fbo.log.CWarningf(ctx, "Couldn't add messages for conv %s: %+v", 9772 id, err) 9773 return nil 9774 } 9775 return nextPage 9776 } 9777 9778 func (fbo *folderBranchOps) recomputeEditHistory( 9779 ctx context.Context, 9780 tlfName tlf.CanonicalName, 9781 nameToID map[string]chat1.ConversationID, 9782 nameToNextPage map[string][]byte) { 9783 gotMore := true 9784 9785 session, err := idutil.GetCurrentSessionIfPossible( 9786 ctx, fbo.config.KBPKI(), true) 9787 if err != nil { 9788 fbo.log.CWarningf(ctx, "Error getting session: %+v", err) 9789 return 9790 } 9791 9792 for gotMore { 9793 // Recompute the history, and fetch more messages for any 9794 // writers who need them. 9795 writersWhoNeedMore := fbo.editHistory.Recompute(string(session.Name)) 9796 gotMore = false 9797 for w, needsMore := range writersWhoNeedMore { 9798 if !needsMore { 9799 continue 9800 } 9801 if startPage, ok := nameToNextPage[w]; ok && startPage != nil { 9802 id, ok := nameToID[w] 9803 if !ok { 9804 fbo.vlog.CLogf( 9805 ctx, libkb.VLog1, "No channel found for %s", w) 9806 continue 9807 } 9808 fbo.vlog.CLogf( 9809 ctx, libkb.VLog1, 9810 "Going to fetch more messages for writer %s", w) 9811 gotMore = true 9812 nextPage := fbo.getEditMessages(ctx, id, w, startPage) 9813 if nextPage == nil { 9814 delete(nameToNextPage, w) 9815 } else { 9816 nameToNextPage[w] = nextPage 9817 } 9818 } 9819 } 9820 } 9821 9822 if fbo.config.IsTestMode() { 9823 // Extra debugging for HOTPOT-1096. 9824 fbo.vlog.CLogf( 9825 ctx, libkb.VLog1, "%p: Recomputing history for %s", 9826 fbo, session.Name) 9827 } 9828 9829 // Update the overall user history. TODO: if the TLF name 9830 // changed, we should clean up the old user history. 9831 fbo.config.UserHistory().UpdateHistory( 9832 tlfName, fbo.id().Type(), fbo.editHistory, string(session.Name)) 9833 } 9834 9835 func (fbo *folderBranchOps) kickOffEditActivityPartialSync( 9836 ctx context.Context, lState *kbfssync.LockState, 9837 rmd ImmutableRootMetadata) (err error) { 9838 if !fbo.config.Mode().EditHistoryPrefetchingEnabled() { 9839 return 9840 } 9841 9842 defer func() { 9843 if err != nil { 9844 fbo.log.CDebugf( 9845 ctx, "Couldn't kick off partial sync for edit activity: %+v", 9846 err) 9847 } 9848 }() 9849 9850 syncConfig, _, err := fbo.getProtocolSyncConfigUnlocked(ctx, lState, rmd) 9851 if err != nil { 9852 return err 9853 } 9854 if syncConfig.Mode != keybase1.FolderSyncMode_DISABLED { 9855 return nil 9856 } 9857 9858 fbo.vlog.CLogf( 9859 ctx, libkb.VLog1, "Kicking off partial sync for revision %d "+ 9860 "due to new edit message", rmd.Revision()) 9861 9862 syncConfig, err = fbo.makeRecentFilesSyncConfig(ctx, rmd) 9863 if err != nil { 9864 return err 9865 } 9866 fbo.kickOffPartialSync(ctx, lState, syncConfig, rmd) 9867 return nil 9868 } 9869 9870 func (fbo *folderBranchOps) handleEditActivity( 9871 ctx context.Context, 9872 a editChannelActivity, 9873 tlfName tlf.CanonicalName, 9874 idToName map[chat1.ConvIDStr]string, 9875 nameToID map[string]chat1.ConversationID, 9876 nameToNextPage map[string][]byte) ( 9877 idToNameRet map[chat1.ConvIDStr]string, 9878 nameToIDRet map[string]chat1.ConversationID, 9879 nameToNextPageRet map[string][]byte, err error) { 9880 var rmd ImmutableRootMetadata 9881 lState := makeFBOLockState() 9882 defer func() { 9883 fbo.recomputeEditHistory(ctx, tlfName, nameToIDRet, nameToNextPageRet) 9884 if rmd != (ImmutableRootMetadata{}) { 9885 _ = fbo.kickOffEditActivityPartialSync(ctx, lState, rmd) 9886 } 9887 fbo.favs.RefreshCacheWhenMTimeChanged(ctx, fbo.id()) 9888 fbo.editActivity.Done() 9889 }() 9890 9891 if a.convID == nil { 9892 fbo.vlog.CLogf(ctx, libkb.VLog1, "Re-initializing chat channels") 9893 return fbo.initEditChatChannels(ctx, tlfName) 9894 } 9895 9896 idStr := a.convID.ConvIDStr() 9897 name, ok := idToName[idStr] 9898 if !ok { 9899 // This is a new channel that we need to monitor. 9900 fbo.config.Chat().RegisterForMessages( 9901 a.convID, fbo.receiveNewEditChat) 9902 idToName[idStr] = a.name 9903 nameToID[a.name] = a.convID 9904 name = a.name 9905 } 9906 if a.message != "" { 9907 fbo.vlog.CLogf(ctx, libkb.VLog1, "New edit message for %s", name) 9908 if fbo.config.IsTestMode() { 9909 // Extra debugging for HOTPOT-1096. 9910 fbo.vlog.CLogf( 9911 ctx, libkb.VLog1, "%p: Processing edit message %s", 9912 fbo, a.message) 9913 } 9914 maxRev, err := fbo.editHistory.AddNotifications( 9915 name, []string{a.message}) 9916 if err != nil { 9917 return nil, nil, nil, err 9918 } 9919 // If the new edits reference the latest merged revision, then 9920 // the MD has already kicked off a partial sync. Since this 9921 // edit might trigger a different partial sync, we should 9922 // start another one as well by setting `rmd` and letting the 9923 // `defer` above kick one off. 9924 latestMergedRev := fbo.getLatestMergedRevision(lState) 9925 if maxRev == latestMergedRev { 9926 rmd, err = GetSingleMD( 9927 ctx, fbo.config, fbo.id(), kbfsmd.NullBranchID, maxRev, 9928 kbfsmd.Merged, nil) 9929 if err != nil { 9930 return nil, nil, nil, err 9931 } 9932 } 9933 } else { 9934 fbo.vlog.CLogf(ctx, libkb.VLog1, "New edit channel for %s", name) 9935 nextPage := fbo.getEditMessages(ctx, a.convID, name, nil) 9936 if nextPage != nil { 9937 nameToNextPage[name] = nextPage 9938 } 9939 } 9940 9941 return idToName, nameToID, nameToNextPage, nil 9942 } 9943 9944 func (fbo *folderBranchOps) refreshEditHistory() { 9945 // If we can't send something to the channel, 9946 // then there is already a refresh pending. 9947 select { 9948 case fbo.refreshEditHistoryChannel <- struct{}{}: 9949 default: 9950 } 9951 } 9952 9953 func (fbo *folderBranchOps) monitorEditsChat(tlfName tlf.CanonicalName) { 9954 ctx, cancelFunc := fbo.newCtxWithFBOID() 9955 defer cancelFunc() 9956 fbo.log.CDebugf(ctx, "Starting kbfs-edits chat monitoring") 9957 9958 monitoringCh := make(chan struct{}) 9959 fbo.editsLock.Lock() 9960 fbo.cancelEdits = cancelFunc 9961 fbo.editMonitoringInProgress = monitoringCh 9962 fbo.editsLock.Unlock() 9963 9964 defer func() { 9965 fbo.editsLock.Lock() 9966 fbo.editMonitoringInProgress = nil 9967 fbo.editsLock.Unlock() 9968 close(monitoringCh) 9969 }() 9970 9971 idToName := make(map[chat1.ConvIDStr]string) 9972 nameToID := make(map[string]chat1.ConversationID) 9973 nameToNextPage := make(map[string][]byte) 9974 9975 for { 9976 select { 9977 case <-fbo.shutdownChan: 9978 fbo.log.CDebugf(ctx, "Shutting down chat monitoring") 9979 return 9980 case <-fbo.refreshEditHistoryChannel: 9981 fbo.recomputeEditHistory(ctx, tlfName, nameToID, nameToNextPage) 9982 case a := <-fbo.editChannels: 9983 var err error 9984 idToName, nameToID, nameToNextPage, err = fbo.handleEditActivity( 9985 ctx, a, tlfName, idToName, nameToID, nameToNextPage) 9986 if err != nil { 9987 fbo.log.CWarningf( 9988 ctx, "Couldn't handle activity %#v: %+v", a, err) 9989 return 9990 } 9991 case <-ctx.Done(): 9992 fbo.log.CDebugf(ctx, "Chat monitoring was canceled") 9993 return 9994 } 9995 } 9996 } 9997 9998 func (fbo *folderBranchOps) addRootNodeWrapper(f func(Node) Node) { 9999 fbo.nodeCache.AddRootWrapper(f) 10000 }