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  }