github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/folder_block_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  	pathlib "path"
    10  	"time"
    11  
    12  	"github.com/keybase/client/go/kbfs/data"
    13  	"github.com/keybase/client/go/kbfs/idutil"
    14  	"github.com/keybase/client/go/kbfs/kbfscodec"
    15  	"github.com/keybase/client/go/kbfs/kbfssync"
    16  	"github.com/keybase/client/go/kbfs/libkey"
    17  	"github.com/keybase/client/go/kbfs/tlf"
    18  	"github.com/keybase/client/go/kbfs/tlfhandle"
    19  	"github.com/keybase/client/go/libkb"
    20  	"github.com/keybase/client/go/logger"
    21  	"github.com/keybase/client/go/protocol/keybase1"
    22  	"github.com/pkg/errors"
    23  	"golang.org/x/net/context"
    24  	"golang.org/x/sync/errgroup"
    25  )
    26  
    27  type overallBlockState int
    28  
    29  const (
    30  	// cleanState: no outstanding local writes.
    31  	cleanState overallBlockState = iota
    32  	// dirtyState: there are outstanding local writes that haven't yet been
    33  	// synced.
    34  	dirtyState
    35  )
    36  
    37  const (
    38  	// numBlockSizeWorkersMax is the max number of workers to use when
    39  	// fetching a set of block sizes.
    40  	numBlockSizeWorkersMax = 50
    41  	// How many pointers to downgrade in a single block size call.
    42  	numBlockSizesPerChunk = 20
    43  	// truncateExtendCutoffPoint is the amount of data in extending
    44  	// truncate that will trigger the extending with a hole algorithm.
    45  	truncateExtendCutoffPoint = 128 * 1024
    46  )
    47  
    48  type mdToCleanIfUnused struct {
    49  	md  ReadOnlyRootMetadata
    50  	bps blockPutStateCopiable
    51  }
    52  
    53  type syncInfo struct {
    54  	oldInfo         data.BlockInfo
    55  	op              *syncOp
    56  	unrefs          []data.BlockInfo
    57  	bps             blockPutStateCopiable
    58  	refBytes        uint64
    59  	unrefBytes      uint64
    60  	toCleanIfUnused []mdToCleanIfUnused
    61  }
    62  
    63  func (si *syncInfo) DeepCopy(
    64  	ctx context.Context, codec kbfscodec.Codec) (newSi *syncInfo, err error) {
    65  	newSi = &syncInfo{
    66  		oldInfo:    si.oldInfo,
    67  		refBytes:   si.refBytes,
    68  		unrefBytes: si.unrefBytes,
    69  	}
    70  	newSi.unrefs = make([]data.BlockInfo, len(si.unrefs))
    71  	copy(newSi.unrefs, si.unrefs)
    72  	if si.bps != nil {
    73  		newSi.bps, err = si.bps.deepCopy(ctx)
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  	}
    78  	if si.op != nil {
    79  		err := kbfscodec.Update(codec, &newSi.op, si.op)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  	}
    84  	newSi.toCleanIfUnused = make([]mdToCleanIfUnused, len(si.toCleanIfUnused))
    85  	for i, toClean := range si.toCleanIfUnused {
    86  		// It might be overkill to deep-copy these MDs and bpses,
    87  		// which are probably immutable, but for now let's do the safe
    88  		// thing.
    89  		copyMd, err := toClean.md.deepCopy(codec)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  		newSi.toCleanIfUnused[i].md = copyMd.ReadOnly()
    94  		newSi.toCleanIfUnused[i].bps, err = toClean.bps.deepCopy(ctx)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  	}
    99  	return newSi, nil
   100  }
   101  
   102  func (si *syncInfo) removeReplacedBlock(ctx context.Context,
   103  	log logger.Logger, ptr data.BlockPointer) {
   104  	for i, ref := range si.op.RefBlocks {
   105  		if ref == ptr {
   106  			log.CDebugf(ctx, "Replacing old ref %v", ptr)
   107  			si.op.RefBlocks = append(si.op.RefBlocks[:i],
   108  				si.op.RefBlocks[i+1:]...)
   109  			for j, unref := range si.unrefs {
   110  				if unref.BlockPointer == ptr {
   111  					si.unrefs = append(si.unrefs[:j], si.unrefs[j+1:]...)
   112  				}
   113  			}
   114  			break
   115  		}
   116  	}
   117  }
   118  
   119  func (si *syncInfo) mergeUnrefCache(md *RootMetadata) {
   120  	for _, info := range si.unrefs {
   121  		// it's ok if we push the same ptr.ID/RefNonce multiple times,
   122  		// because the subsequent ones should have a QuotaSize of 0.
   123  		md.AddUnrefBlock(info)
   124  	}
   125  }
   126  
   127  type deferredState struct {
   128  	// Writes and truncates for blocks that were being sync'd, and
   129  	// need to be replayed after the sync finishes on top of the new
   130  	// versions of the blocks.
   131  	writes []func(
   132  		context.Context, *kbfssync.LockState, KeyMetadataWithRootDirEntry,
   133  		data.Path) error
   134  	// Blocks that need to be deleted from the dirty cache before any
   135  	// deferred writes are replayed.
   136  	dirtyDeletes []data.BlockPointer
   137  	waitBytes    int64
   138  }
   139  
   140  // folderBlockOps contains all the fields that must be synchronized by
   141  // blockLock. It will eventually also contain all the methods that
   142  // must be synchronized by blockLock, so that folderBranchOps will
   143  // have no knowledge of blockLock.
   144  //
   145  // -- And now, a primer on tracking dirty bytes --
   146  //
   147  // The DirtyBlockCache tracks the number of bytes that are dirtied
   148  // system-wide, as the number of bytes that haven't yet been synced
   149  // ("unsynced"), and a number of bytes that haven't yet been resolved
   150  // yet because the overall file Sync hasn't finished yet ("total").
   151  // This data helps us decide when we need to block incoming Writes, in
   152  // order to keep memory usage from exploding.
   153  //
   154  // It's the responsibility of folderBlockOps (and its helper struct
   155  // dirtyFile) to update these totals in DirtyBlockCache for the
   156  // individual files within this TLF.  This is complicated by a few things:
   157  //   - New writes to a file are "deferred" while a Sync is happening, and
   158  //     are replayed after the Sync finishes.
   159  //   - Syncs can be canceled or error out halfway through syncing the blocks,
   160  //     leaving the file in a dirty state until the next Sync.
   161  //   - Syncs can fail with a /recoverable/ error, in which case they get
   162  //     retried automatically by folderBranchOps.  In that case, the retried
   163  //     Sync also sucks in any outstanding deferred writes.
   164  //
   165  // With all that in mind, here is the rough breakdown of how this
   166  // bytes-tracking is implemented:
   167  //   - On a Write/Truncate to a block, folderBranchOps counts all the
   168  //     newly-dirtied bytes in a file as "unsynced".  That is, if the block was
   169  //     already in the dirty cache (and not already being synced), only
   170  //     extensions to the block count as "unsynced" bytes.
   171  //   - When a Sync starts, dirtyFile remembers the total of bytes being synced,
   172  //     and the size of each block being synced.
   173  //   - When each block put finishes successfully, dirtyFile subtracts the size
   174  //     of that block from "unsynced".
   175  //   - When a Sync finishes successfully, the total sum of bytes in that sync
   176  //     are subtracted from the "total" dirty bytes outstanding.
   177  //   - If a Sync fails, but some blocks were put successfully, those blocks
   178  //     are "re-dirtied", which means they count as unsynced bytes again.
   179  //     dirtyFile handles this.
   180  //   - When a Write/Truncate is deferred due to an ongoing Sync, its bytes
   181  //     still count towards the "unsynced" total.  In fact, this essentially
   182  //     creates a new copy of those blocks, and the whole size of that block
   183  //     (not just the newly-dirtied bytes) count for the total.  However,
   184  //     when the write gets replayed, folderBlockOps first subtracts those bytes
   185  //     from the system-wide numbers, since they are about to be replayed.
   186  //   - When a Sync is retried after a recoverable failure, dirtyFile adds
   187  //     the newly-dirtied deferred bytes to the system-wide numbers, since they
   188  //     are now being assimilated into this Sync.
   189  //   - dirtyFile also exposes a concept of "orphaned" blocks.  These are child
   190  //     blocks being synced that are now referenced via a new, permanent block
   191  //     ID from the parent indirect block.  This matters for when hard failures
   192  //     occur during a Sync -- the blocks will no longer be accessible under
   193  //     their previous old pointers, and so dirtyFile needs to know their old
   194  //     bytes can be cleaned up now.
   195  type folderBlockOps struct {
   196  	config       Config
   197  	log          logger.Logger
   198  	vlog         *libkb.VDebugLog
   199  	folderBranch data.FolderBranch
   200  	observers    *observerList
   201  
   202  	// forceSyncChan can be sent on to trigger an immediate
   203  	// Sync().  It is a blocking channel.
   204  	forceSyncChan chan<- struct{}
   205  
   206  	// protects access to blocks in this folder and all fields
   207  	// below.
   208  	blockLock blockLock
   209  
   210  	// Which files are currently dirty and have dirty blocks that are either
   211  	// currently syncing, or waiting to be sync'd.
   212  	dirtyFiles map[data.BlockPointer]*data.DirtyFile
   213  
   214  	// For writes and truncates, track the unsynced to-be-unref'd
   215  	// block infos, per-path.
   216  	unrefCache map[data.BlockRef]*syncInfo
   217  
   218  	// dirtyDirs track which directories are currently dirty in this
   219  	// TLF.
   220  	dirtyDirs          map[data.BlockPointer][]data.BlockInfo
   221  	dirtyDirsSyncing   bool
   222  	deferredDirUpdates []func(lState *kbfssync.LockState) error
   223  
   224  	// dirtyRootDirEntry is a DirEntry representing the root of the
   225  	// TLF (to be copied into the RootMetadata on a sync).
   226  	dirtyRootDirEntry *data.DirEntry
   227  
   228  	chargedTo keybase1.UserOrTeamID
   229  
   230  	// Track deferred operations on a per-file basis.
   231  	deferred map[data.BlockRef]deferredState
   232  
   233  	// set to true if this write or truncate should be deferred
   234  	doDeferWrite bool
   235  
   236  	// While this channel is non-nil and non-closed, writes get blocked.
   237  	holdNewWritesCh <-chan struct{}
   238  
   239  	// nodeCache itself is goroutine-safe, but write/truncate must
   240  	// call PathFromNode() only under blockLock (see nodeCache
   241  	// comments in folder_branch_ops.go).
   242  	nodeCache NodeCache
   243  }
   244  
   245  // Only exported methods of folderBlockOps should be used outside of this
   246  // file.
   247  //
   248  // Although, temporarily, folderBranchOps is allowed to reach in and
   249  // manipulate folderBlockOps fields and methods directly.
   250  
   251  func (fbo *folderBlockOps) id() tlf.ID {
   252  	return fbo.folderBranch.Tlf
   253  }
   254  
   255  func (fbo *folderBlockOps) branch() data.BranchName {
   256  	return fbo.folderBranch.Branch
   257  }
   258  
   259  func (fbo *folderBlockOps) isSyncedTlf() bool {
   260  	return fbo.branch() == data.MasterBranch && fbo.config.IsSyncedTlf(fbo.id())
   261  }
   262  
   263  // GetState returns the overall block state of this TLF.
   264  func (fbo *folderBlockOps) GetState(
   265  	lState *kbfssync.LockState) overallBlockState {
   266  	fbo.blockLock.RLock(lState)
   267  	defer fbo.blockLock.RUnlock(lState)
   268  	if len(fbo.dirtyFiles) == 0 && len(fbo.dirtyDirs) == 0 &&
   269  		fbo.dirtyRootDirEntry == nil {
   270  		return cleanState
   271  	}
   272  	return dirtyState
   273  }
   274  
   275  // getCleanEncodedBlockSizesLocked retrieves the encoded sizes and
   276  // block statuses of the clean blocks pointed to each of the block
   277  // pointers in `ptrs`, which must be valid, either from the cache or
   278  // from the server.  If `rtype` is `blockReadParallel`, it's assumed
   279  // that some coordinating goroutine is holding the correct locks, and
   280  // in that case `lState` must be `nil`.
   281  func (fbo *folderBlockOps) getCleanEncodedBlockSizesLocked(ctx context.Context,
   282  	lState *kbfssync.LockState, kmd libkey.KeyMetadata,
   283  	ptrs []data.BlockPointer, branch data.BranchName,
   284  	rtype data.BlockReqType, assumeCacheIsLive bool) (
   285  	sizes []uint32, statuses []keybase1.BlockStatus, err error) {
   286  	if rtype != data.BlockReadParallel {
   287  		if rtype == data.BlockWrite {
   288  			panic("Cannot get the size of a block for writing")
   289  		}
   290  		fbo.blockLock.AssertAnyLocked(lState)
   291  	} else if lState != nil {
   292  		panic("Non-nil lState passed to getCleanEncodedBlockSizeLocked " +
   293  			"with blockReadParallel")
   294  	}
   295  
   296  	sizes = make([]uint32, len(ptrs))
   297  	statuses = make([]keybase1.BlockStatus, len(ptrs))
   298  	var toFetchIndices []int
   299  	var ptrsToFetch []data.BlockPointer
   300  	for i, ptr := range ptrs {
   301  		if !ptr.IsValid() {
   302  			return nil, nil, InvalidBlockRefError{ptr.Ref()}
   303  		}
   304  
   305  		if assumeCacheIsLive {
   306  			// If we're assuming all blocks in the cache are live, we just
   307  			// need to get the block size, which we can do from either one
   308  			// of the caches.
   309  			if block, err := fbo.config.BlockCache().Get(ptr); err == nil {
   310  				sizes[i] = block.GetEncodedSize()
   311  				statuses[i] = keybase1.BlockStatus_LIVE
   312  				continue
   313  			}
   314  			if diskBCache := fbo.config.DiskBlockCache(); diskBCache != nil {
   315  				cacheType := DiskBlockAnyCache
   316  				if fbo.isSyncedTlf() {
   317  					cacheType = DiskBlockSyncCache
   318  				}
   319  				if buf, _, _, err := diskBCache.Get(
   320  					ctx, fbo.id(), ptr.ID, cacheType); err == nil {
   321  					sizes[i] = uint32(len(buf))
   322  					statuses[i] = keybase1.BlockStatus_LIVE
   323  					continue
   324  				}
   325  			}
   326  		}
   327  
   328  		if err := checkDataVersion(fbo.config, data.Path{}, ptr); err != nil {
   329  			return nil, nil, err
   330  		}
   331  
   332  		// Fetch this block from the server.
   333  		ptrsToFetch = append(ptrsToFetch, ptr)
   334  		toFetchIndices = append(toFetchIndices, i)
   335  	}
   336  
   337  	defer func() {
   338  		fbo.vlog.CLogf(
   339  			ctx, libkb.VLog1, "GetEncodedSizes ptrs=%v sizes=%d statuses=%s: "+
   340  				"%+v", ptrs, sizes, statuses, err)
   341  		if err != nil {
   342  			return
   343  		}
   344  
   345  		// In certain testing situations, a block might be represented
   346  		// with a 0 size in our journal or be missing from our local
   347  		// data stores, and we need to reconstruct the size using the
   348  		// cache in order to make the accounting work out for the test.
   349  		for i, ptr := range ptrs {
   350  			if sizes[i] == 0 {
   351  				if block, cerr := fbo.config.BlockCache().Get(
   352  					ptr); cerr == nil {
   353  					fbo.vlog.CLogf(
   354  						ctx, libkb.VLog1,
   355  						"Fixing encoded size of %v with cached copy", ptr)
   356  					sizes[i] = block.GetEncodedSize()
   357  				}
   358  			}
   359  		}
   360  	}()
   361  
   362  	// Unlock the blockLock while we wait for the network, only if
   363  	// it's locked for reading by a single goroutine.  If it's locked
   364  	// for writing, that indicates we are performing an atomic write
   365  	// operation, and we need to ensure that nothing else comes in and
   366  	// modifies the blocks, so don't unlock.
   367  	//
   368  	// If there may be multiple goroutines fetching blocks under the
   369  	// same lState, we can't safely unlock since some of the other
   370  	// goroutines may be operating on the data assuming they have the
   371  	// lock.
   372  	bops := fbo.config.BlockOps()
   373  	var fetchedSizes []uint32
   374  	var fetchedStatuses []keybase1.BlockStatus
   375  	if rtype != data.BlockReadParallel && rtype != data.BlockLookup {
   376  		fbo.blockLock.DoRUnlockedIfPossible(lState, func(*kbfssync.LockState) {
   377  			fetchedSizes, fetchedStatuses, err = bops.GetEncodedSizes(
   378  				ctx, kmd, ptrsToFetch)
   379  		})
   380  	} else {
   381  		fetchedSizes, fetchedStatuses, err = bops.GetEncodedSizes(
   382  			ctx, kmd, ptrsToFetch)
   383  	}
   384  	if err != nil {
   385  		return nil, nil, err
   386  	}
   387  
   388  	for i, j := range toFetchIndices {
   389  		sizes[j] = fetchedSizes[i]
   390  		statuses[j] = fetchedStatuses[i]
   391  	}
   392  
   393  	return sizes, statuses, nil
   394  }
   395  
   396  // getBlockHelperLocked retrieves the block pointed to by ptr, which
   397  // must be valid, either from the cache or from the server. If
   398  // notifyPath is valid and the block isn't cached, trigger a read
   399  // notification.  If `rtype` is `blockReadParallel`, it's assumed that
   400  // some coordinating goroutine is holding the correct locks, and
   401  // in that case `lState` must be `nil`.
   402  //
   403  // This must be called only by get{File,Dir}BlockHelperLocked().
   404  func (fbo *folderBlockOps) getBlockHelperLocked(ctx context.Context,
   405  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, ptr data.BlockPointer,
   406  	branch data.BranchName, newBlock makeNewBlock, lifetime data.BlockCacheLifetime,
   407  	notifyPath data.Path, rtype data.BlockReqType) (data.Block, error) {
   408  	if rtype != data.BlockReadParallel {
   409  		fbo.blockLock.AssertAnyLocked(lState)
   410  	} else if lState != nil {
   411  		panic("Non-nil lState passed to getBlockHelperLocked " +
   412  			"with blockReadParallel")
   413  	}
   414  
   415  	if !ptr.IsValid() {
   416  		return nil, InvalidBlockRefError{ptr.Ref()}
   417  	}
   418  
   419  	if block, err := fbo.config.DirtyBlockCache().Get(
   420  		ctx, fbo.id(), ptr, branch); err == nil {
   421  		return block, nil
   422  	}
   423  
   424  	if block, lifetime, err := fbo.config.BlockCache().GetWithLifetime(ptr); err == nil {
   425  		if lifetime != data.PermanentEntry {
   426  			// If the block was cached in the past, and is not a permanent
   427  			// block (i.e., currently being written by the user), we need
   428  			// to handle it as if it's an on-demand request so that its
   429  			// downstream prefetches are triggered correctly according to
   430  			// the new on-demand fetch priority.
   431  			action := fbo.config.Mode().DefaultBlockRequestAction()
   432  			if fbo.isSyncedTlf() {
   433  				action = action.AddSync()
   434  			}
   435  			prefetchStatus := fbo.config.PrefetchStatus(ctx, fbo.id(), ptr)
   436  			fbo.config.BlockOps().Prefetcher().ProcessBlockForPrefetch(ctx, ptr,
   437  				block, kmd, defaultOnDemandRequestPriority-1, lifetime,
   438  				prefetchStatus, action)
   439  		}
   440  		return block, nil
   441  	}
   442  
   443  	if err := checkDataVersion(fbo.config, notifyPath, ptr); err != nil {
   444  		return nil, err
   445  	}
   446  
   447  	if notifyPath.IsValidForNotification() {
   448  		fbo.config.Reporter().Notify(ctx, readNotification(notifyPath, false))
   449  		defer fbo.config.Reporter().Notify(ctx,
   450  			readNotification(notifyPath, true))
   451  	}
   452  
   453  	// Unlock the blockLock while we wait for the network, only if
   454  	// it's locked for reading by a single goroutine.  If it's locked
   455  	// for writing, that indicates we are performing an atomic write
   456  	// operation, and we need to ensure that nothing else comes in and
   457  	// modifies the blocks, so don't unlock.
   458  	//
   459  	// If there may be multiple goroutines fetching blocks under the
   460  	// same lState, we can't safely unlock since some of the other
   461  	// goroutines may be operating on the data assuming they have the
   462  	// lock.
   463  	// fetch the block, and add to cache
   464  	block := newBlock()
   465  	bops := fbo.config.BlockOps()
   466  	var err error
   467  	if rtype != data.BlockReadParallel && rtype != data.BlockLookup {
   468  		fbo.blockLock.DoRUnlockedIfPossible(lState, func(*kbfssync.LockState) {
   469  			err = bops.Get(ctx, kmd, ptr, block, lifetime, fbo.branch())
   470  		})
   471  	} else {
   472  		err = bops.Get(ctx, kmd, ptr, block, lifetime, fbo.branch())
   473  	}
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  
   478  	return block, nil
   479  }
   480  
   481  // getFileBlockHelperLocked retrieves the block pointed to by ptr,
   482  // which must be valid, either from an internal cache, the block
   483  // cache, or from the server. An error is returned if the retrieved
   484  // block is not a file block.  If `rtype` is `blockReadParallel`, it's
   485  // assumed that some coordinating goroutine is holding the correct
   486  // locks, and in that case `lState` must be `nil`.
   487  //
   488  // This must be called only by GetFileBlockForReading(),
   489  // getFileBlockLocked(), and getFileLocked().
   490  //
   491  // p is used only when reporting errors and sending read
   492  // notifications, and can be empty.
   493  func (fbo *folderBlockOps) getFileBlockHelperLocked(ctx context.Context,
   494  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, ptr data.BlockPointer,
   495  	branch data.BranchName, p data.Path, rtype data.BlockReqType) (
   496  	*data.FileBlock, error) {
   497  	if rtype != data.BlockReadParallel {
   498  		fbo.blockLock.AssertAnyLocked(lState)
   499  	} else if lState != nil {
   500  		panic("Non-nil lState passed to getFileBlockHelperLocked " +
   501  			"with blockReadParallel")
   502  	}
   503  
   504  	block, err := fbo.getBlockHelperLocked(
   505  		ctx, lState, kmd, ptr, branch, data.NewFileBlock, data.TransientEntry, p, rtype)
   506  	if err != nil {
   507  		return nil, err
   508  	}
   509  
   510  	fblock, ok := block.(*data.FileBlock)
   511  	if !ok {
   512  		return nil, NotFileBlockError{ptr, branch, p}
   513  	}
   514  
   515  	return fblock, nil
   516  }
   517  
   518  // GetCleanEncodedBlocksSizeSum retrieves the sum of the encoded sizes
   519  // of the blocks pointed to by ptrs, all of which must be valid,
   520  // either from the cache or from the server.
   521  //
   522  // The caller can specify a set of pointers using
   523  // `ignoreRecoverableForRemovalErrors` for which "recoverable" fetch
   524  // errors are tolerated.  In that case, the returned sum will not
   525  // include the size for any pointers in the
   526  // `ignoreRecoverableForRemovalErrors` set that hit such an error.
   527  //
   528  // This should be called for "internal" operations, like conflict
   529  // resolution and state checking, which don't know what kind of block
   530  // the pointers refer to.  Any downloaded blocks will not be cached,
   531  // if they weren't in the cache already.
   532  //
   533  // If `onlyCountIfLive` is true, the sum includes blocks that the
   534  // bserver thinks are currently reachable from the merged branch
   535  // (i.e., un-archived).
   536  func (fbo *folderBlockOps) GetCleanEncodedBlocksSizeSum(ctx context.Context,
   537  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, ptrs []data.BlockPointer,
   538  	ignoreRecoverableForRemovalErrors map[data.BlockPointer]bool,
   539  	branch data.BranchName, onlyCountIfLive bool) (uint64, error) {
   540  	fbo.blockLock.RLock(lState)
   541  	defer fbo.blockLock.RUnlock(lState)
   542  
   543  	ptrCh := make(chan []data.BlockPointer, len(ptrs))
   544  	sumCh := make(chan uint32, len(ptrs))
   545  
   546  	numChunks := (len(ptrs) + numBlockSizesPerChunk - 1) /
   547  		numBlockSizesPerChunk
   548  	numWorkers := numBlockSizeWorkersMax
   549  	if numChunks < numWorkers {
   550  		numWorkers = numChunks
   551  	}
   552  
   553  	currChunk := make([]data.BlockPointer, 0, numBlockSizesPerChunk)
   554  	for _, ptr := range ptrs {
   555  		currChunk = append(currChunk, ptr)
   556  		if len(currChunk) == numBlockSizesPerChunk {
   557  			ptrCh <- currChunk
   558  			currChunk = make([]data.BlockPointer, 0, numBlockSizesPerChunk)
   559  		}
   560  	}
   561  	if len(currChunk) > 0 {
   562  		ptrCh <- currChunk
   563  	}
   564  
   565  	// If we don't care if something's live or not, there's no reason
   566  	// not to use the cached block.
   567  	assumeCacheIsLive := !onlyCountIfLive
   568  	eg, groupCtx := errgroup.WithContext(ctx)
   569  	for i := 0; i < numWorkers; i++ {
   570  		eg.Go(func() error {
   571  			for ptrs := range ptrCh {
   572  				sizes, statuses, err := fbo.getCleanEncodedBlockSizesLocked(
   573  					groupCtx, nil, kmd, ptrs, branch,
   574  					data.BlockReadParallel, assumeCacheIsLive)
   575  				for i, ptr := range ptrs {
   576  					// TODO: we might be able to recover the size of the
   577  					// top-most block of a removed file using the merged
   578  					// directory entry, the same way we do in
   579  					// `folderBranchOps.unrefEntry`.
   580  					if isRecoverableBlockErrorForRemoval(err) &&
   581  						ignoreRecoverableForRemovalErrors[ptr] {
   582  						fbo.log.CDebugf(
   583  							groupCtx, "Hit an ignorable, recoverable "+
   584  								"error for block %v: %v", ptr, err)
   585  						continue
   586  					}
   587  					if err != nil {
   588  						return err
   589  					}
   590  
   591  					if onlyCountIfLive &&
   592  						statuses[i] != keybase1.BlockStatus_LIVE {
   593  						sumCh <- 0
   594  					} else {
   595  						sumCh <- sizes[i]
   596  					}
   597  				}
   598  			}
   599  			return nil
   600  		})
   601  	}
   602  	close(ptrCh)
   603  
   604  	if err := eg.Wait(); err != nil {
   605  		return 0, err
   606  	}
   607  	close(sumCh)
   608  
   609  	var sum uint64
   610  	for size := range sumCh {
   611  		sum += uint64(size)
   612  	}
   613  	return sum, nil
   614  }
   615  
   616  // getDirBlockHelperLocked retrieves the block pointed to by ptr, which
   617  // must be valid, either from the cache or from the server. An error
   618  // is returned if the retrieved block is not a dir block.
   619  //
   620  // This must be called only by GetDirBlockForReading() and
   621  // getDirLocked().
   622  //
   623  // p is used only when reporting errors, and can be empty.
   624  func (fbo *folderBlockOps) getDirBlockHelperLocked(ctx context.Context,
   625  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, ptr data.BlockPointer,
   626  	branch data.BranchName, p data.Path, rtype data.BlockReqType) (*data.DirBlock, error) {
   627  	if rtype != data.BlockReadParallel {
   628  		fbo.blockLock.AssertAnyLocked(lState)
   629  	}
   630  
   631  	// Check data version explicitly here, with the right path, since
   632  	// we pass an empty path below.
   633  	if err := checkDataVersion(fbo.config, p, ptr); err != nil {
   634  		return nil, err
   635  	}
   636  
   637  	// Pass in an empty notify path because notifications should only
   638  	// trigger for file reads.
   639  	block, err := fbo.getBlockHelperLocked(
   640  		ctx, lState, kmd, ptr, branch, data.NewDirBlock, data.TransientEntry,
   641  		data.Path{}, rtype)
   642  	if err != nil {
   643  		return nil, err
   644  	}
   645  
   646  	dblock, ok := block.(*data.DirBlock)
   647  	if !ok {
   648  		return nil, NotDirBlockError{ptr, branch, p}
   649  	}
   650  
   651  	return dblock, nil
   652  }
   653  
   654  // GetFileBlockForReading retrieves the block pointed to by ptr, which
   655  // must be valid, either from the cache or from the server. An error
   656  // is returned if the retrieved block is not a file block.
   657  //
   658  // This should be called for "internal" operations, like conflict
   659  // resolution and state checking. "Real" operations should use
   660  // getFileBlockLocked() and getFileLocked() instead.
   661  //
   662  // p is used only when reporting errors, and can be empty.
   663  func (fbo *folderBlockOps) GetFileBlockForReading(ctx context.Context,
   664  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, ptr data.BlockPointer,
   665  	branch data.BranchName, p data.Path) (*data.FileBlock, error) {
   666  	fbo.blockLock.RLock(lState)
   667  	defer fbo.blockLock.RUnlock(lState)
   668  	return fbo.getFileBlockHelperLocked(
   669  		ctx, lState, kmd, ptr, branch, p, data.BlockRead)
   670  }
   671  
   672  // GetDirBlockForReading retrieves the block pointed to by ptr, which
   673  // must be valid, either from the cache or from the server. An error
   674  // is returned if the retrieved block is not a dir block.
   675  //
   676  // This should be called for "internal" operations, like conflict
   677  // resolution and state checking. "Real" operations should use
   678  // getDirLocked() instead.
   679  //
   680  // p is used only when reporting errors, and can be empty.
   681  func (fbo *folderBlockOps) GetDirBlockForReading(ctx context.Context,
   682  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, ptr data.BlockPointer,
   683  	branch data.BranchName, p data.Path) (*data.DirBlock, error) {
   684  	fbo.blockLock.RLock(lState)
   685  	defer fbo.blockLock.RUnlock(lState)
   686  	return fbo.getDirBlockHelperLocked(
   687  		ctx, lState, kmd, ptr, branch, p, data.BlockRead)
   688  }
   689  
   690  // getFileBlockLocked retrieves the block pointed to by ptr, which
   691  // must be valid, either from the cache or from the server. An error
   692  // is returned if the retrieved block is not a file block.
   693  //
   694  // The given path must be valid, and the given pointer must be its
   695  // tail pointer or an indirect pointer from it. A read notification is
   696  // triggered for the given path only if the block isn't in the cache.
   697  //
   698  // This shouldn't be called for "internal" operations, like conflict
   699  // resolution and state checking -- use GetFileBlockForReading() for
   700  // those instead.
   701  //
   702  // When rtype == blockWrite and the cached version of the block is
   703  // currently clean, or the block is currently being synced, this
   704  // method makes a copy of the file block and returns it.  If this
   705  // method might be called again for the same block within a single
   706  // operation, it is the caller's responsibility to write that block
   707  // back to the cache as dirty.
   708  //
   709  // Note that blockLock must be locked exactly when rtype ==
   710  // blockWrite, and must be r-locked when rtype == blockRead.  (This
   711  // differs from getDirLocked.)  This is because a write operation
   712  // (like write, truncate and sync which lock blockLock) fetching a
   713  // file block will almost always need to modify that block, and so
   714  // will pass in blockWrite.  If rtype == blockReadParallel, it's
   715  // assumed that some coordinating goroutine is holding the correct
   716  // locks, and in that case `lState` must be `nil`.
   717  //
   718  // file is used only when reporting errors and sending read
   719  // notifications, and can be empty except that file.Branch must be set
   720  // correctly.
   721  //
   722  // This method also returns whether the block was already dirty.
   723  func (fbo *folderBlockOps) getFileBlockLocked(ctx context.Context,
   724  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, ptr data.BlockPointer,
   725  	file data.Path, rtype data.BlockReqType) (
   726  	fblock *data.FileBlock, wasDirty bool, err error) {
   727  	switch rtype {
   728  	case data.BlockRead:
   729  		fbo.blockLock.AssertRLocked(lState)
   730  	case data.BlockWrite:
   731  		fbo.blockLock.AssertLocked(lState)
   732  	case data.BlockReadParallel:
   733  		// This goroutine might not be the official lock holder, so
   734  		// don't make any assertions.
   735  		if lState != nil {
   736  			panic("Non-nil lState passed to getFileBlockLocked " +
   737  				"with blockReadParallel")
   738  		}
   739  	case data.BlockLookup:
   740  		panic("blockLookup should only be used for directory blocks")
   741  	default:
   742  		panic(fmt.Sprintf("Unknown block req type: %d", rtype))
   743  	}
   744  
   745  	fblock, err = fbo.getFileBlockHelperLocked(
   746  		ctx, lState, kmd, ptr, file.Branch, file, rtype)
   747  	if err != nil {
   748  		return nil, false, err
   749  	}
   750  
   751  	wasDirty = fbo.config.DirtyBlockCache().IsDirty(fbo.id(), ptr, file.Branch)
   752  	if rtype == data.BlockWrite {
   753  		// Copy the block if it's for writing, and either the
   754  		// block is not yet dirty or the block is currently
   755  		// being sync'd and needs a copy even though it's
   756  		// already dirty.
   757  		df := fbo.dirtyFiles[file.TailPointer()]
   758  		if !wasDirty || (df != nil && df.BlockNeedsCopy(ptr)) {
   759  			fblock = fblock.DeepCopy()
   760  		}
   761  	}
   762  	return fblock, wasDirty, nil
   763  }
   764  
   765  // getFileLocked is getFileBlockLocked called with file.tailPointer().
   766  func (fbo *folderBlockOps) getFileLocked(ctx context.Context,
   767  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, file data.Path,
   768  	rtype data.BlockReqType) (*data.FileBlock, error) {
   769  	// Callers should have already done this check, but it doesn't
   770  	// hurt to do it again.
   771  	if !file.IsValid() {
   772  		return nil, errors.WithStack(InvalidPathError{file})
   773  	}
   774  	fblock, _, err := fbo.getFileBlockLocked(
   775  		ctx, lState, kmd, file.TailPointer(), file, rtype)
   776  	return fblock, err
   777  }
   778  
   779  func (fbo *folderBlockOps) getIndirectFileBlockInfosLocked(
   780  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata,
   781  	file data.Path) ([]data.BlockInfo, error) {
   782  	fbo.blockLock.AssertRLocked(lState)
   783  	var id keybase1.UserOrTeamID // Data reads don't depend on the id.
   784  	fd := fbo.newFileData(lState, file, id, kmd)
   785  	return fd.GetIndirectFileBlockInfos(ctx)
   786  }
   787  
   788  // GetIndirectFileBlockInfos returns a list of BlockInfos for all
   789  // indirect blocks of the given file. If the returned error is a
   790  // recoverable one (as determined by
   791  // isRecoverableBlockErrorForRemoval), the returned list may still be
   792  // non-empty, and holds all the BlockInfos for all found indirect
   793  // blocks.
   794  func (fbo *folderBlockOps) GetIndirectFileBlockInfos(ctx context.Context,
   795  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, file data.Path) (
   796  	[]data.BlockInfo, error) {
   797  	fbo.blockLock.RLock(lState)
   798  	defer fbo.blockLock.RUnlock(lState)
   799  	return fbo.getIndirectFileBlockInfosLocked(ctx, lState, kmd, file)
   800  }
   801  
   802  // GetIndirectDirBlockInfos returns a list of BlockInfos for all
   803  // indirect blocks of the given directory. If the returned error is a
   804  // recoverable one (as determined by
   805  // isRecoverableBlockErrorForRemoval), the returned list may still be
   806  // non-empty, and holds all the BlockInfos for all found indirect
   807  // blocks.
   808  func (fbo *folderBlockOps) GetIndirectDirBlockInfos(
   809  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata,
   810  	dir data.Path) ([]data.BlockInfo, error) {
   811  	fbo.blockLock.RLock(lState)
   812  	defer fbo.blockLock.RUnlock(lState)
   813  	var id keybase1.UserOrTeamID // Data reads don't depend on the id.
   814  	fd := fbo.newDirDataLocked(lState, dir, id, kmd)
   815  	return fd.GetIndirectDirBlockInfos(ctx)
   816  }
   817  
   818  // GetIndirectFileBlockInfosWithTopBlock returns a list of BlockInfos
   819  // for all indirect blocks of the given file, starting from the given
   820  // top-most block. If the returned error is a recoverable one (as
   821  // determined by isRecoverableBlockErrorForRemoval), the returned list
   822  // may still be non-empty, and holds all the BlockInfos for all found
   823  // indirect blocks. (This will be relevant when we handle multiple
   824  // levels of indirection.)
   825  func (fbo *folderBlockOps) GetIndirectFileBlockInfosWithTopBlock(
   826  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata, file data.Path,
   827  	topBlock *data.FileBlock) (
   828  	[]data.BlockInfo, error) {
   829  	fbo.blockLock.RLock(lState)
   830  	defer fbo.blockLock.RUnlock(lState)
   831  	var id keybase1.UserOrTeamID // Data reads don't depend on the id.
   832  	fd := fbo.newFileData(lState, file, id, kmd)
   833  	return fd.GetIndirectFileBlockInfosWithTopBlock(ctx, topBlock)
   834  }
   835  
   836  func (fbo *folderBlockOps) getChargedToLocked(
   837  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata) (
   838  	keybase1.UserOrTeamID, error) {
   839  	fbo.blockLock.AssertAnyLocked(lState)
   840  	if !fbo.chargedTo.IsNil() {
   841  		return fbo.chargedTo, nil
   842  	}
   843  	chargedTo, err := chargedToForTLF(
   844  		ctx, fbo.config.KBPKI(), fbo.config.KBPKI(), fbo.config,
   845  		kmd.GetTlfHandle())
   846  	if err != nil {
   847  		return keybase1.UserOrTeamID(""), err
   848  	}
   849  	fbo.chargedTo = chargedTo
   850  	return chargedTo, nil
   851  }
   852  
   853  // ClearChargedTo clears out the cached chargedTo UID for this FBO.
   854  func (fbo *folderBlockOps) ClearChargedTo(lState *kbfssync.LockState) {
   855  	fbo.blockLock.Lock(lState)
   856  	defer fbo.blockLock.Unlock(lState)
   857  	fbo.chargedTo = keybase1.UserOrTeamID("")
   858  }
   859  
   860  // DeepCopyFile makes a complete copy of the given file, deduping leaf
   861  // blocks and making new random BlockPointers for all indirect blocks.
   862  // It returns the new top pointer of the copy, and all the new child
   863  // pointers in the copy.  It takes a custom DirtyBlockCache, which
   864  // directs where the resulting block copies are stored.
   865  func (fbo *folderBlockOps) deepCopyFileLocked(
   866  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata, file data.Path,
   867  	dirtyBcache data.DirtyBlockCacheSimple, dataVer data.Ver) (
   868  	newTopPtr data.BlockPointer, allChildPtrs []data.BlockPointer, err error) {
   869  	// Deep copying doesn't alter any data in use, it only makes copy,
   870  	// so only a read lock is needed.
   871  	fbo.blockLock.AssertRLocked(lState)
   872  	chargedTo, err := chargedToForTLF(
   873  		ctx, fbo.config.KBPKI(), fbo.config.KBPKI(), fbo.config,
   874  		kmd.GetTlfHandle())
   875  	if err != nil {
   876  		return data.BlockPointer{}, nil, err
   877  	}
   878  	fd := fbo.newFileDataWithCache(
   879  		lState, file, chargedTo, kmd, dirtyBcache)
   880  	return fd.DeepCopy(ctx, dataVer)
   881  }
   882  
   883  func (fbo *folderBlockOps) cacheHashBehavior() data.BlockCacheHashBehavior {
   884  	return cacheHashBehavior(fbo.config, fbo.config, fbo.id())
   885  }
   886  
   887  func (fbo *folderBlockOps) UndupChildrenInCopy(ctx context.Context,
   888  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, file data.Path, bps blockPutState,
   889  	dirtyBcache data.DirtyBlockCacheSimple, topBlock *data.FileBlock) (
   890  	[]data.BlockInfo, error) {
   891  	fbo.blockLock.Lock(lState)
   892  	defer fbo.blockLock.Unlock(lState)
   893  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
   894  	if err != nil {
   895  		return nil, err
   896  	}
   897  	fd := fbo.newFileDataWithCache(
   898  		lState, file, chargedTo, kmd, dirtyBcache)
   899  	return fd.UndupChildrenInCopy(ctx, fbo.config.BlockCache(),
   900  		fbo.config.BlockOps(), bps, topBlock, fbo.cacheHashBehavior())
   901  }
   902  
   903  func (fbo *folderBlockOps) ReadyNonLeafBlocksInCopy(ctx context.Context,
   904  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, file data.Path, bps blockPutState,
   905  	dirtyBcache data.DirtyBlockCacheSimple, topBlock *data.FileBlock) (
   906  	[]data.BlockInfo, error) {
   907  	fbo.blockLock.RLock(lState)
   908  	defer fbo.blockLock.RUnlock(lState)
   909  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
   910  	if err != nil {
   911  		return nil, err
   912  	}
   913  
   914  	fd := fbo.newFileDataWithCache(
   915  		lState, file, chargedTo, kmd, dirtyBcache)
   916  	return fd.ReadyNonLeafBlocksInCopy(ctx, fbo.config.BlockCache(),
   917  		fbo.config.BlockOps(), bps, topBlock, fbo.cacheHashBehavior())
   918  }
   919  
   920  // getDirLocked retrieves the block pointed to by the tail pointer of
   921  // the given path, which must be valid, either from the cache or from
   922  // the server. An error is returned if the retrieved block is not a
   923  // dir block.
   924  //
   925  // This shouldn't be called for "internal" operations, like conflict
   926  // resolution and state checking -- use GetDirBlockForReading() for
   927  // those instead.
   928  //
   929  // When rtype == blockWrite and the cached version of the block is
   930  // currently clean, this method makes a copy of the directory block
   931  // and returns it.  If this method might be called again for the same
   932  // block within a single operation, it is the caller's responsibility
   933  // to write that block back to the cache as dirty.
   934  //
   935  // Note that blockLock must be either r-locked or locked, but
   936  // independently of rtype. (This differs from getFileLocked and
   937  // getFileBlockLocked.) File write operations (which lock blockLock)
   938  // don't need a copy of parent dir blocks, and non-file write
   939  // operations do need to copy dir blocks for modifications.
   940  func (fbo *folderBlockOps) getDirLocked(ctx context.Context,
   941  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, ptr data.BlockPointer, dir data.Path,
   942  	rtype data.BlockReqType) (*data.DirBlock, bool, error) {
   943  	switch rtype {
   944  	case data.BlockRead, data.BlockWrite, data.BlockLookup:
   945  		fbo.blockLock.AssertAnyLocked(lState)
   946  	case data.BlockReadParallel:
   947  		// This goroutine might not be the official lock holder, so
   948  		// don't make any assertions.
   949  		if lState != nil {
   950  			panic("Non-nil lState passed to getFileBlockLocked " +
   951  				"with blockReadParallel")
   952  		}
   953  	default:
   954  		panic(fmt.Sprintf("Unknown block req type: %d", rtype))
   955  	}
   956  
   957  	// Callers should have already done this check, but it doesn't
   958  	// hurt to do it again.
   959  	if !dir.IsValid() {
   960  		return nil, false, errors.WithStack(InvalidPathError{dir})
   961  	}
   962  
   963  	// Get the block for the last element in the path.
   964  	dblock, err := fbo.getDirBlockHelperLocked(
   965  		ctx, lState, kmd, ptr, dir.Branch, dir, rtype)
   966  	if err != nil {
   967  		return nil, false, err
   968  	}
   969  
   970  	wasDirty := fbo.config.DirtyBlockCache().IsDirty(fbo.id(), ptr, dir.Branch)
   971  	if rtype == data.BlockWrite && !wasDirty {
   972  		// Copy the block if it's for writing and the block is
   973  		// not yet dirty.
   974  		dblock = dblock.DeepCopy()
   975  	}
   976  	return dblock, wasDirty, nil
   977  }
   978  
   979  // GetDir retrieves the block pointed to by the tail pointer of the
   980  // given path, which must be valid, either from the cache or from the
   981  // server. An error is returned if the retrieved block is not a dir
   982  // block.
   983  //
   984  // This shouldn't be called for "internal" operations, like conflict
   985  // resolution and state checking -- use GetDirBlockForReading() for
   986  // those instead.
   987  //
   988  // When rtype == blockWrite and the cached version of the block is
   989  // currently clean, this method makes a copy of the directory block
   990  // and returns it.  If this method might be called again for the same
   991  // block within a single operation, it is the caller's responsibility
   992  // to write that block back to the cache as dirty.
   993  func (fbo *folderBlockOps) GetDir(
   994  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata, dir data.Path,
   995  	rtype data.BlockReqType) (*data.DirBlock, error) {
   996  	fbo.blockLock.RLock(lState)
   997  	defer fbo.blockLock.RUnlock(lState)
   998  	dblock, _, err := fbo.getDirLocked(
   999  		ctx, lState, kmd, dir.TailPointer(), dir, rtype)
  1000  	return dblock, err
  1001  }
  1002  
  1003  type dirCacheUndoFn func(lState *kbfssync.LockState)
  1004  
  1005  func (fbo *folderBlockOps) wrapWithBlockLock(fn func()) dirCacheUndoFn {
  1006  	return func(lState *kbfssync.LockState) {
  1007  		if fn == nil {
  1008  			return
  1009  		}
  1010  		fbo.blockLock.Lock(lState)
  1011  		defer fbo.blockLock.Unlock(lState)
  1012  		fn()
  1013  	}
  1014  }
  1015  
  1016  func (fbo *folderBlockOps) newDirDataLocked(lState *kbfssync.LockState,
  1017  	dir data.Path, chargedTo keybase1.UserOrTeamID, kmd libkey.KeyMetadata) *data.DirData {
  1018  	fbo.blockLock.AssertAnyLocked(lState)
  1019  	return data.NewDirData(dir, chargedTo, fbo.config.BlockSplitter(), kmd,
  1020  		func(ctx context.Context, kmd libkey.KeyMetadata, ptr data.BlockPointer,
  1021  			dir data.Path, rtype data.BlockReqType) (*data.DirBlock, bool, error) {
  1022  			lState := lState
  1023  			if rtype == data.BlockReadParallel {
  1024  				lState = nil
  1025  			}
  1026  			return fbo.getDirLocked(
  1027  				ctx, lState, kmd, ptr, dir, rtype)
  1028  		},
  1029  		func(ctx context.Context, ptr data.BlockPointer, block data.Block) error {
  1030  			return fbo.config.DirtyBlockCache().Put(
  1031  				ctx, fbo.id(), ptr, dir.Branch, block)
  1032  		}, fbo.log, fbo.vlog)
  1033  }
  1034  
  1035  // newDirDataWithDBMLocked creates a new `dirData` that reads from and
  1036  // puts into a local dir block cache.  If it reads a block out from
  1037  // anything but the `dbm`, it makes a copy of it before inserting it
  1038  // into the `dbm`.
  1039  func (fbo *folderBlockOps) newDirDataWithDBMLocked(lState *kbfssync.LockState,
  1040  	dir data.Path, chargedTo keybase1.UserOrTeamID, kmd libkey.KeyMetadata,
  1041  	dbm dirBlockMap) *data.DirData {
  1042  	fbo.blockLock.AssertRLocked(lState)
  1043  	return data.NewDirData(dir, chargedTo, fbo.config.BlockSplitter(), kmd,
  1044  		func(ctx context.Context, kmd libkey.KeyMetadata, ptr data.BlockPointer,
  1045  			dir data.Path, rtype data.BlockReqType) (*data.DirBlock, bool, error) {
  1046  			hasBlock, err := dbm.hasBlock(ctx, ptr)
  1047  			if err != nil {
  1048  				return nil, false, err
  1049  			}
  1050  			if hasBlock {
  1051  				block, err := dbm.getBlock(ctx, ptr)
  1052  				if err != nil {
  1053  					return nil, false, err
  1054  				}
  1055  				return block, true, nil
  1056  			}
  1057  
  1058  			localLState := lState
  1059  			getRtype := rtype
  1060  			switch rtype {
  1061  			case data.BlockReadParallel:
  1062  				localLState = nil
  1063  			case data.BlockWrite:
  1064  				getRtype = data.BlockRead
  1065  			}
  1066  
  1067  			block, wasDirty, err := fbo.getDirLocked(
  1068  				ctx, localLState, kmd, ptr, dir, getRtype)
  1069  			if err != nil {
  1070  				return nil, false, err
  1071  			}
  1072  
  1073  			if rtype == data.BlockWrite {
  1074  				// Make a copy before we stick it in the local block cache.
  1075  				block = block.DeepCopy()
  1076  				err = dbm.putBlock(ctx, ptr, block)
  1077  				if err != nil {
  1078  					return nil, false, err
  1079  				}
  1080  			}
  1081  			return block, wasDirty, nil
  1082  		},
  1083  		func(ctx context.Context, ptr data.BlockPointer, block data.Block) error {
  1084  			return dbm.putBlock(ctx, ptr, block.(*data.DirBlock))
  1085  		}, fbo.log, fbo.vlog)
  1086  }
  1087  
  1088  // newDirDataWithDBM is like `newDirDataWithDBMLocked`, but it must be
  1089  // called with `blockLock` unlocked, and the returned function must be
  1090  // called when the returned `dirData` is no longer in use.
  1091  func (fbo *folderBlockOps) newDirDataWithDBM(
  1092  	lState *kbfssync.LockState, dir data.Path, chargedTo keybase1.UserOrTeamID,
  1093  	kmd libkey.KeyMetadata, dbm dirBlockMap) (*data.DirData, func()) {
  1094  	// Lock and fetch for reading only, we want any dirty
  1095  	// blocks to go into the dbm.
  1096  	fbo.blockLock.RLock(lState)
  1097  	cleanupFn := func() { fbo.blockLock.RUnlock(lState) }
  1098  	return fbo.newDirDataWithDBMLocked(lState, dir, chargedTo, kmd, dbm),
  1099  		cleanupFn
  1100  }
  1101  
  1102  func (fbo *folderBlockOps) makeDirDirtyLocked(
  1103  	lState *kbfssync.LockState, ptr data.BlockPointer, unrefs []data.BlockInfo) func() {
  1104  	fbo.blockLock.AssertLocked(lState)
  1105  	oldUnrefs, wasDirty := fbo.dirtyDirs[ptr]
  1106  	oldLen := len(oldUnrefs)
  1107  	fbo.dirtyDirs[ptr] = append(oldUnrefs, unrefs...)
  1108  	return func() {
  1109  		dirtyBcache := fbo.config.DirtyBlockCache()
  1110  		if wasDirty {
  1111  			fbo.dirtyDirs[ptr] = oldUnrefs[:oldLen:oldLen]
  1112  		} else {
  1113  			_ = dirtyBcache.Delete(fbo.id(), ptr, fbo.branch())
  1114  			delete(fbo.dirtyDirs, ptr)
  1115  		}
  1116  		for _, unref := range unrefs {
  1117  			_ = dirtyBcache.Delete(fbo.id(), unref.BlockPointer, fbo.branch())
  1118  		}
  1119  	}
  1120  }
  1121  
  1122  func (fbo *folderBlockOps) updateParentDirEntryLocked(
  1123  	ctx context.Context, lState *kbfssync.LockState, dir data.Path,
  1124  	kmd KeyMetadataWithRootDirEntry, setMtime, setCtime bool) (func(), error) {
  1125  	fbo.blockLock.AssertLocked(lState)
  1126  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  1127  	if err != nil {
  1128  		return nil, err
  1129  	}
  1130  	now := fbo.nowUnixNano()
  1131  	pp := *dir.ParentPath()
  1132  	if pp.IsValid() {
  1133  		dd := fbo.newDirDataLocked(lState, pp, chargedTo, kmd)
  1134  		de, err := dd.Lookup(ctx, dir.TailName())
  1135  		if err != nil {
  1136  			return nil, err
  1137  		}
  1138  		newDe := de
  1139  		if setMtime {
  1140  			newDe.Mtime = now
  1141  		}
  1142  		if setCtime {
  1143  			newDe.Ctime = now
  1144  		}
  1145  		unrefs, err := dd.UpdateEntry(ctx, dir.TailName(), newDe)
  1146  		if err != nil {
  1147  			return nil, err
  1148  		}
  1149  		undoDirtyFn := fbo.makeDirDirtyLocked(lState, pp.TailPointer(), unrefs)
  1150  		return func() {
  1151  			_, _ = dd.UpdateEntry(ctx, dir.TailName(), de)
  1152  			undoDirtyFn()
  1153  		}, nil
  1154  	}
  1155  
  1156  	// If the parent isn't a valid path, we need to update the root entry.
  1157  	var de *data.DirEntry
  1158  	if fbo.dirtyRootDirEntry == nil {
  1159  		deCopy := kmd.GetRootDirEntry()
  1160  		fbo.dirtyRootDirEntry = &deCopy
  1161  	} else {
  1162  		deCopy := *fbo.dirtyRootDirEntry
  1163  		de = &deCopy
  1164  	}
  1165  	if setMtime {
  1166  		fbo.dirtyRootDirEntry.Mtime = now
  1167  	}
  1168  	if setCtime {
  1169  		fbo.dirtyRootDirEntry.Ctime = now
  1170  	}
  1171  	return func() {
  1172  		fbo.dirtyRootDirEntry = de
  1173  	}, nil
  1174  }
  1175  
  1176  func (fbo *folderBlockOps) addDirEntryInCacheLocked(
  1177  	ctx context.Context, lState *kbfssync.LockState,
  1178  	kmd KeyMetadataWithRootDirEntry, dir data.Path, newName data.PathPartString,
  1179  	newDe data.DirEntry) (func(), error) {
  1180  	fbo.blockLock.AssertLocked(lState)
  1181  
  1182  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  1183  	if err != nil {
  1184  		return nil, err
  1185  	}
  1186  	dd := fbo.newDirDataLocked(lState, dir, chargedTo, kmd)
  1187  	unrefs, err := dd.AddEntry(ctx, newName, newDe)
  1188  	if err != nil {
  1189  		return nil, err
  1190  	}
  1191  	parentUndo, err := fbo.updateParentDirEntryLocked(
  1192  		ctx, lState, dir, kmd, true, true)
  1193  	if err != nil {
  1194  		_, _ = dd.RemoveEntry(ctx, newName)
  1195  		return nil, err
  1196  	}
  1197  
  1198  	undoDirtyFn := fbo.makeDirDirtyLocked(lState, dir.TailPointer(), unrefs)
  1199  	return func() {
  1200  		_, _ = dd.RemoveEntry(ctx, newName)
  1201  		undoDirtyFn()
  1202  		parentUndo()
  1203  	}, nil
  1204  }
  1205  
  1206  // AddDirEntryInCache adds a brand new entry to the given directory
  1207  // and updates the directory's own mtime and ctime.  It returns a
  1208  // function that can be called if the change needs to be undone.
  1209  func (fbo *folderBlockOps) AddDirEntryInCache(
  1210  	ctx context.Context, lState *kbfssync.LockState,
  1211  	kmd KeyMetadataWithRootDirEntry, dir data.Path, newName data.PathPartString,
  1212  	newDe data.DirEntry) (dirCacheUndoFn, error) {
  1213  	fbo.blockLock.Lock(lState)
  1214  	defer fbo.blockLock.Unlock(lState)
  1215  	fn, err := fbo.addDirEntryInCacheLocked(
  1216  		ctx, lState, kmd, dir, newName, newDe)
  1217  	if err != nil {
  1218  		return nil, err
  1219  	}
  1220  	return fbo.wrapWithBlockLock(fn), nil
  1221  }
  1222  
  1223  func (fbo *folderBlockOps) removeDirEntryInCacheLocked(
  1224  	ctx context.Context, lState *kbfssync.LockState,
  1225  	kmd KeyMetadataWithRootDirEntry, dir data.Path, oldName data.PathPartString,
  1226  	oldDe data.DirEntry) (func(), error) {
  1227  	fbo.blockLock.AssertLocked(lState)
  1228  
  1229  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  1230  	if err != nil {
  1231  		return nil, err
  1232  	}
  1233  	dd := fbo.newDirDataLocked(lState, dir, chargedTo, kmd)
  1234  	unrefs, err := dd.RemoveEntry(ctx, oldName)
  1235  	if err != nil {
  1236  		return nil, err
  1237  	}
  1238  	if oldDe.Type == data.Dir {
  1239  		// The parent dir inherits any dirty unrefs from the removed
  1240  		// directory.
  1241  		if childUnrefs, ok := fbo.dirtyDirs[oldDe.BlockPointer]; ok {
  1242  			unrefs = append(unrefs, childUnrefs...)
  1243  		}
  1244  	}
  1245  
  1246  	unlinkUndoFn := fbo.nodeCache.Unlink(
  1247  		oldDe.Ref(), dir.ChildPath(
  1248  			oldName, oldDe.BlockPointer, fbo.nodeCache.ObfuscatorMaker()()),
  1249  		oldDe)
  1250  
  1251  	parentUndo, err := fbo.updateParentDirEntryLocked(
  1252  		ctx, lState, dir, kmd, true, true)
  1253  	if err != nil {
  1254  		if unlinkUndoFn != nil {
  1255  			unlinkUndoFn()
  1256  		}
  1257  		_, _ = dd.AddEntry(ctx, oldName, oldDe)
  1258  		return nil, err
  1259  	}
  1260  
  1261  	undoDirtyFn := fbo.makeDirDirtyLocked(lState, dir.TailPointer(), unrefs)
  1262  	return func() {
  1263  		_, _ = dd.AddEntry(ctx, oldName, oldDe)
  1264  		if undoDirtyFn != nil {
  1265  			undoDirtyFn()
  1266  		}
  1267  		if parentUndo != nil {
  1268  			parentUndo()
  1269  		}
  1270  		if unlinkUndoFn != nil {
  1271  			unlinkUndoFn()
  1272  		}
  1273  	}, nil
  1274  }
  1275  
  1276  // RemoveDirEntryInCache removes an entry from the given directory //
  1277  // and updates the directory's own mtime and ctime.  It returns a
  1278  // function that can be called if the change needs to be undone.
  1279  func (fbo *folderBlockOps) RemoveDirEntryInCache(
  1280  	ctx context.Context, lState *kbfssync.LockState,
  1281  	kmd KeyMetadataWithRootDirEntry, dir data.Path, oldName data.PathPartString,
  1282  	oldDe data.DirEntry) (dirCacheUndoFn, error) {
  1283  	fbo.blockLock.Lock(lState)
  1284  	defer fbo.blockLock.Unlock(lState)
  1285  	fn, err := fbo.removeDirEntryInCacheLocked(
  1286  		ctx, lState, kmd, dir, oldName, oldDe)
  1287  	if err != nil {
  1288  		return nil, err
  1289  	}
  1290  	return fbo.wrapWithBlockLock(fn), nil
  1291  }
  1292  
  1293  // RenameDirEntryInCache updates the entries of both the old and new
  1294  // parent dirs for the given target dir atomically (with respect to
  1295  // blockLock).  It also updates the cache entry for the target, which
  1296  // would have its Ctime changed. The updates will get applied to the
  1297  // dirty blocks on subsequent fetches.
  1298  //
  1299  // The returned bool indicates whether or not the caller should clean
  1300  // up the target cache entry when the effects of the operation are no
  1301  // longer needed.
  1302  func (fbo *folderBlockOps) RenameDirEntryInCache(
  1303  	ctx context.Context, lState *kbfssync.LockState,
  1304  	kmd KeyMetadataWithRootDirEntry, oldParent data.Path,
  1305  	oldName data.PathPartString, newParent data.Path,
  1306  	newName data.PathPartString, newDe data.DirEntry,
  1307  	replacedDe data.DirEntry) (undo dirCacheUndoFn, err error) {
  1308  	fbo.blockLock.Lock(lState)
  1309  	defer fbo.blockLock.Unlock(lState)
  1310  	if newParent.TailPointer() == oldParent.TailPointer() &&
  1311  		oldName == newName {
  1312  		// Noop
  1313  		return nil, nil
  1314  	}
  1315  
  1316  	var undoReplace func()
  1317  	if replacedDe.IsInitialized() {
  1318  		undoReplace, err = fbo.removeDirEntryInCacheLocked(
  1319  			ctx, lState, kmd, newParent, newName, replacedDe)
  1320  		if err != nil {
  1321  			return nil, err
  1322  		}
  1323  	}
  1324  	defer func() {
  1325  		if err != nil && undoReplace != nil {
  1326  			undoReplace()
  1327  		}
  1328  	}()
  1329  
  1330  	undoAdd, err := fbo.addDirEntryInCacheLocked(
  1331  		ctx, lState, kmd, newParent, newName, newDe)
  1332  	if err != nil {
  1333  		return nil, err
  1334  	}
  1335  	defer func() {
  1336  		if err != nil && undoAdd != nil {
  1337  			undoAdd()
  1338  		}
  1339  	}()
  1340  
  1341  	undoRm, err := fbo.removeDirEntryInCacheLocked(
  1342  		ctx, lState, kmd, oldParent, oldName, data.DirEntry{})
  1343  	if err != nil {
  1344  		return nil, err
  1345  	}
  1346  	defer func() {
  1347  		if err != nil && undoRm != nil {
  1348  			undoRm()
  1349  		}
  1350  	}()
  1351  
  1352  	newParentNode := fbo.nodeCache.Get(newParent.TailRef())
  1353  	undoMove, err := fbo.nodeCache.Move(newDe.Ref(), newParentNode, newName)
  1354  	if err != nil {
  1355  		return nil, err
  1356  	}
  1357  
  1358  	return fbo.wrapWithBlockLock(func() {
  1359  		if undoMove != nil {
  1360  			undoMove()
  1361  		}
  1362  		if undoRm != nil {
  1363  			undoRm()
  1364  		}
  1365  		if undoAdd != nil {
  1366  			undoAdd()
  1367  		}
  1368  		if undoReplace != nil {
  1369  			undoReplace()
  1370  		}
  1371  	}), nil
  1372  }
  1373  
  1374  func (fbo *folderBlockOps) setCachedAttrLocked(
  1375  	ctx context.Context, lState *kbfssync.LockState,
  1376  	kmd KeyMetadataWithRootDirEntry, dir data.Path, name data.PathPartString,
  1377  	attr attrChange, realEntry data.DirEntry) (dirCacheUndoFn, error) {
  1378  	fbo.blockLock.AssertLocked(lState)
  1379  
  1380  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  1381  	if err != nil {
  1382  		return nil, err
  1383  	}
  1384  
  1385  	if !dir.IsValid() {
  1386  		// Can't set attrs directly on the root entry, primarily
  1387  		// because there's no way to indicate it's dirty.  TODO: allow
  1388  		// mtime-setting on the root dir?
  1389  		return nil, InvalidParentPathError{dir}
  1390  	}
  1391  	var de data.DirEntry
  1392  	var unlinkedNode Node
  1393  
  1394  	dd := fbo.newDirDataLocked(lState, dir, chargedTo, kmd)
  1395  	de, err = dd.Lookup(ctx, name)
  1396  	if _, noExist := errors.Cause(err).(idutil.NoSuchNameError); noExist {
  1397  		// The node may be unlinked.
  1398  		unlinkedNode = fbo.nodeCache.Get(realEntry.Ref())
  1399  		if unlinkedNode != nil && !fbo.nodeCache.IsUnlinked(unlinkedNode) {
  1400  			unlinkedNode = nil
  1401  		}
  1402  		if unlinkedNode != nil {
  1403  			de = fbo.nodeCache.UnlinkedDirEntry(unlinkedNode)
  1404  		} else {
  1405  			return nil, err
  1406  		}
  1407  	} else if err != nil {
  1408  		return nil, err
  1409  	}
  1410  
  1411  	oldDe := de
  1412  	switch attr {
  1413  	case exAttr:
  1414  		de.Type = realEntry.Type
  1415  	case mtimeAttr:
  1416  		de.Mtime = realEntry.Mtime
  1417  	}
  1418  	de.Ctime = realEntry.Ctime
  1419  
  1420  	var undoDirtyFn func()
  1421  	if unlinkedNode != nil {
  1422  		fbo.nodeCache.UpdateUnlinkedDirEntry(unlinkedNode, de)
  1423  	} else {
  1424  		unrefs, err := dd.UpdateEntry(ctx, name, de)
  1425  		if err != nil {
  1426  			return nil, err
  1427  		}
  1428  		undoDirtyFn = fbo.makeDirDirtyLocked(lState, dir.TailPointer(), unrefs)
  1429  	}
  1430  
  1431  	return fbo.wrapWithBlockLock(func() {
  1432  		if unlinkedNode != nil {
  1433  			fbo.nodeCache.UpdateUnlinkedDirEntry(unlinkedNode, oldDe)
  1434  		} else {
  1435  			_, _ = dd.UpdateEntry(ctx, name, oldDe)
  1436  			undoDirtyFn()
  1437  		}
  1438  	}), nil
  1439  }
  1440  
  1441  // SetAttrInDirEntryInCache updates an entry from the given directory.
  1442  func (fbo *folderBlockOps) SetAttrInDirEntryInCache(
  1443  	ctx context.Context, lState *kbfssync.LockState,
  1444  	kmd KeyMetadataWithRootDirEntry, p data.Path, newDe data.DirEntry,
  1445  	attr attrChange) (dirCacheUndoFn, error) {
  1446  	fbo.blockLock.Lock(lState)
  1447  	defer fbo.blockLock.Unlock(lState)
  1448  	return fbo.setCachedAttrLocked(
  1449  		ctx, lState, kmd, *p.ParentPath(), p.TailName(), attr, newDe)
  1450  }
  1451  
  1452  // getDirtyDirLocked composes getDirLocked and
  1453  // updateWithDirtyEntriesLocked. Note that a dirty dir means that it
  1454  // has entries possibly pointing to dirty files, and/or that its
  1455  // children list is dirty.
  1456  func (fbo *folderBlockOps) getDirtyDirLocked(ctx context.Context,
  1457  	lState *kbfssync.LockState, kmd libkey.KeyMetadata, dir data.Path, rtype data.BlockReqType) (
  1458  	*data.DirBlock, error) {
  1459  	fbo.blockLock.AssertAnyLocked(lState)
  1460  
  1461  	dblock, _, err := fbo.getDirLocked(
  1462  		ctx, lState, kmd, dir.TailPointer(), dir, rtype)
  1463  	if err != nil {
  1464  		return nil, err
  1465  	}
  1466  	return dblock, err
  1467  }
  1468  
  1469  // GetDirtyDirCopy returns a deep copy of the directory block for a
  1470  // dirty directory, while under lock, updated with all cached dirty
  1471  // entries.
  1472  func (fbo *folderBlockOps) GetDirtyDirCopy(
  1473  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata, dir data.Path,
  1474  	rtype data.BlockReqType) (*data.DirBlock, error) {
  1475  	fbo.blockLock.RLock(lState)
  1476  	defer fbo.blockLock.RUnlock(lState)
  1477  	dblock, err := fbo.getDirtyDirLocked(ctx, lState, kmd, dir, rtype)
  1478  	if err != nil {
  1479  		return nil, err
  1480  	}
  1481  	// Copy it while under lock.  Otherwise, another operation like
  1482  	// `Write` can modify it while the caller is trying to copy it,
  1483  	// leading to a panic like in KBFS-3407.
  1484  	return dblock.DeepCopy(), nil
  1485  }
  1486  
  1487  // GetChildren returns a map of EntryInfos for the (possibly dirty)
  1488  // children entries of the given directory.
  1489  func (fbo *folderBlockOps) GetChildren(
  1490  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata,
  1491  	dir data.Path) (map[data.PathPartString]data.EntryInfo, error) {
  1492  	fbo.blockLock.RLock(lState)
  1493  	defer fbo.blockLock.RUnlock(lState)
  1494  	dd := fbo.newDirDataLocked(lState, dir, keybase1.UserOrTeamID(""), kmd)
  1495  	return dd.GetChildren(ctx)
  1496  }
  1497  
  1498  // GetEntries returns a map of DirEntries for the (possibly dirty)
  1499  // children entries of the given directory.
  1500  func (fbo *folderBlockOps) GetEntries(
  1501  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata,
  1502  	dir data.Path) (map[data.PathPartString]data.DirEntry, error) {
  1503  	fbo.blockLock.RLock(lState)
  1504  	defer fbo.blockLock.RUnlock(lState)
  1505  	dd := fbo.newDirDataLocked(lState, dir, keybase1.UserOrTeamID(""), kmd)
  1506  	return dd.GetEntries(ctx)
  1507  }
  1508  
  1509  func (fbo *folderBlockOps) getEntryLocked(ctx context.Context,
  1510  	lState *kbfssync.LockState, kmd KeyMetadataWithRootDirEntry, file data.Path,
  1511  	includeDeleted bool) (de data.DirEntry, err error) {
  1512  	fbo.blockLock.AssertAnyLocked(lState)
  1513  
  1514  	// See if this is the root.
  1515  	if !file.HasValidParent() {
  1516  		if fbo.dirtyRootDirEntry != nil {
  1517  			return *fbo.dirtyRootDirEntry, nil
  1518  		}
  1519  		return kmd.GetRootDirEntry(), nil
  1520  	}
  1521  
  1522  	dd := fbo.newDirDataLocked(
  1523  		lState, *file.ParentPath(), keybase1.UserOrTeamID(""), kmd)
  1524  	de, err = dd.Lookup(ctx, file.TailName())
  1525  	_, noExist := errors.Cause(err).(idutil.NoSuchNameError)
  1526  	if includeDeleted && (noExist || de.BlockPointer != file.TailPointer()) {
  1527  		unlinkedNode := fbo.nodeCache.Get(file.TailPointer().Ref())
  1528  		if unlinkedNode != nil && fbo.nodeCache.IsUnlinked(unlinkedNode) {
  1529  			return fbo.nodeCache.UnlinkedDirEntry(unlinkedNode), nil
  1530  		}
  1531  		return data.DirEntry{}, err
  1532  	} else if err != nil {
  1533  		return data.DirEntry{}, err
  1534  	}
  1535  	return de, nil
  1536  }
  1537  
  1538  // file must have a valid parent.
  1539  func (fbo *folderBlockOps) updateEntryLocked(ctx context.Context,
  1540  	lState *kbfssync.LockState, kmd KeyMetadataWithRootDirEntry, file data.Path,
  1541  	de data.DirEntry, includeDeleted bool) error {
  1542  	fbo.blockLock.AssertAnyLocked(lState)
  1543  
  1544  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  1545  	if err != nil {
  1546  		return err
  1547  	}
  1548  	parentPath := *file.ParentPath()
  1549  	dd := fbo.newDirDataLocked(lState, parentPath, chargedTo, kmd)
  1550  	unrefs, err := dd.UpdateEntry(ctx, file.TailName(), de)
  1551  	_, noExist := errors.Cause(err).(idutil.NoSuchNameError)
  1552  	switch {
  1553  	case noExist && includeDeleted:
  1554  		unlinkedNode := fbo.nodeCache.Get(file.TailPointer().Ref())
  1555  		if unlinkedNode != nil && fbo.nodeCache.IsUnlinked(unlinkedNode) {
  1556  			fbo.nodeCache.UpdateUnlinkedDirEntry(unlinkedNode, de)
  1557  			return nil
  1558  		}
  1559  		return err
  1560  	case err != nil:
  1561  		return err
  1562  	default:
  1563  		_ = fbo.makeDirDirtyLocked(lState, parentPath.TailPointer(), unrefs)
  1564  	}
  1565  
  1566  	// If we're in the middle of syncing the directories, but the
  1567  	// current file is not yet being synced, we need to re-apply this
  1568  	// update after the sync is done, so it doesn't get lost after the
  1569  	// syncing directory block is readied.  This only applies to dir
  1570  	// updates being caused by file changes; other types of dir writes
  1571  	// are protected by `folderBranchOps.syncLock`, which is held
  1572  	// during `SyncAll`.
  1573  	if fbo.dirtyDirsSyncing && !fbo.doDeferWrite {
  1574  		fbo.log.CDebugf(ctx, "Deferring update entry during sync")
  1575  		n := fbo.nodeCache.Get(file.TailRef())
  1576  		fbo.deferredDirUpdates = append(
  1577  			fbo.deferredDirUpdates, func(lState *kbfssync.LockState) error {
  1578  				file := fbo.nodeCache.PathFromNode(n)
  1579  				de.BlockPointer = file.TailPointer()
  1580  				return fbo.updateEntryLocked(
  1581  					ctx, lState, kmd, file, de, includeDeleted)
  1582  			})
  1583  	}
  1584  
  1585  	return nil
  1586  }
  1587  
  1588  // GetEntry returns the possibly-dirty DirEntry of the given file in
  1589  // its parent DirBlock. file must have a valid parent.
  1590  func (fbo *folderBlockOps) GetEntry(
  1591  	ctx context.Context, lState *kbfssync.LockState,
  1592  	kmd KeyMetadataWithRootDirEntry, file data.Path) (data.DirEntry, error) {
  1593  	fbo.blockLock.RLock(lState)
  1594  	defer fbo.blockLock.RUnlock(lState)
  1595  	return fbo.getEntryLocked(ctx, lState, kmd, file, false)
  1596  }
  1597  
  1598  // GetEntryEvenIfDeleted returns the possibly-dirty DirEntry of the
  1599  // given file in its parent DirBlock, even if the file has been
  1600  // deleted. file must have a valid parent.
  1601  func (fbo *folderBlockOps) GetEntryEvenIfDeleted(
  1602  	ctx context.Context, lState *kbfssync.LockState,
  1603  	kmd KeyMetadataWithRootDirEntry, file data.Path) (data.DirEntry, error) {
  1604  	fbo.blockLock.RLock(lState)
  1605  	defer fbo.blockLock.RUnlock(lState)
  1606  	return fbo.getEntryLocked(ctx, lState, kmd, file, true)
  1607  }
  1608  
  1609  func (fbo *folderBlockOps) getChildNodeLocked(
  1610  	lState *kbfssync.LockState, dir Node, name data.PathPartString,
  1611  	de data.DirEntry) (Node, error) {
  1612  	fbo.blockLock.AssertRLocked(lState)
  1613  
  1614  	if de.Type == data.Sym {
  1615  		return nil, nil
  1616  	}
  1617  
  1618  	return fbo.nodeCache.GetOrCreate(de.BlockPointer, name, dir, de.Type)
  1619  }
  1620  
  1621  func (fbo *folderBlockOps) GetChildNode(
  1622  	lState *kbfssync.LockState, dir Node, name data.PathPartString,
  1623  	de data.DirEntry) (Node, error) {
  1624  	fbo.blockLock.RLock(lState)
  1625  	defer fbo.blockLock.RUnlock(lState)
  1626  	return fbo.getChildNodeLocked(lState, dir, name, de)
  1627  }
  1628  
  1629  // Lookup returns the possibly-dirty DirEntry of the given file in its
  1630  // parent DirBlock, and a Node for the file if it exists.  It has to
  1631  // do all of this under the block lock to avoid races with
  1632  // UpdatePointers.
  1633  func (fbo *folderBlockOps) Lookup(
  1634  	ctx context.Context, lState *kbfssync.LockState,
  1635  	kmd KeyMetadataWithRootDirEntry, dir Node, name data.PathPartString) (
  1636  	Node, data.DirEntry, error) {
  1637  	fbo.blockLock.RLock(lState)
  1638  	defer fbo.blockLock.RUnlock(lState)
  1639  
  1640  	// Protect against non-dir nodes being passed in by mistake.
  1641  	// TODO: we should make this a more specific error probably, but
  1642  	// then we need to update some places that check for
  1643  	// `NoSuchNameError` to check for this one as well.
  1644  	if dir.EntryType() != data.Dir {
  1645  		fbo.log.CDebugf(
  1646  			ctx, "Got unexpected node type when looking up %s: %s",
  1647  			name, dir.EntryType())
  1648  		return nil, data.DirEntry{}, idutil.NoSuchNameError{Name: name.String()}
  1649  	}
  1650  
  1651  	dirPath := fbo.nodeCache.PathFromNode(dir)
  1652  	if !dirPath.IsValid() {
  1653  		return nil, data.DirEntry{}, errors.WithStack(InvalidPathError{dirPath})
  1654  	}
  1655  
  1656  	childPath := dirPath.ChildPathNoPtr(name, fbo.nodeCache.ObfuscatorMaker()())
  1657  	de, err := fbo.getEntryLocked(ctx, lState, kmd, childPath, false)
  1658  	if err != nil {
  1659  		return nil, data.DirEntry{}, err
  1660  	}
  1661  
  1662  	node, err := fbo.getChildNodeLocked(lState, dir, name, de)
  1663  	if err != nil {
  1664  		return nil, data.DirEntry{}, err
  1665  	}
  1666  	return node, de, nil
  1667  }
  1668  
  1669  func (fbo *folderBlockOps) getOrCreateDirtyFileLocked(
  1670  	lState *kbfssync.LockState, file data.Path) *data.DirtyFile {
  1671  	fbo.blockLock.AssertLocked(lState)
  1672  	ptr := file.TailPointer()
  1673  	df := fbo.dirtyFiles[ptr]
  1674  	if df == nil {
  1675  		df = data.NewDirtyFile(file, fbo.config.DirtyBlockCache())
  1676  		fbo.dirtyFiles[ptr] = df
  1677  	}
  1678  	return df
  1679  }
  1680  
  1681  // cacheBlockIfNotYetDirtyLocked puts a block into the cache, but only
  1682  // does so if the block isn't already marked as dirty in the cache.
  1683  // This is useful when operating on a dirty copy of a block that may
  1684  // already be in the cache.
  1685  func (fbo *folderBlockOps) cacheBlockIfNotYetDirtyLocked(
  1686  	ctx context.Context, lState *kbfssync.LockState, ptr data.BlockPointer,
  1687  	file data.Path, block data.Block) error {
  1688  	fbo.blockLock.AssertLocked(lState)
  1689  	df := fbo.getOrCreateDirtyFileLocked(lState, file)
  1690  	needsCaching, isSyncing := df.SetBlockDirty(ptr)
  1691  
  1692  	if needsCaching {
  1693  		err := fbo.config.DirtyBlockCache().Put(
  1694  			ctx, fbo.id(), ptr, file.Branch, block)
  1695  		if err != nil {
  1696  			return err
  1697  		}
  1698  	}
  1699  
  1700  	if isSyncing {
  1701  		fbo.doDeferWrite = true
  1702  	}
  1703  	return nil
  1704  }
  1705  
  1706  func (fbo *folderBlockOps) getOrCreateSyncInfoLocked(
  1707  	lState *kbfssync.LockState, de data.DirEntry) (*syncInfo, error) {
  1708  	fbo.blockLock.AssertLocked(lState)
  1709  	ref := de.Ref()
  1710  	si, ok := fbo.unrefCache[ref]
  1711  	if !ok {
  1712  		so, err := newSyncOp(de.BlockPointer)
  1713  		if err != nil {
  1714  			return nil, err
  1715  		}
  1716  		si = &syncInfo{
  1717  			oldInfo: de.BlockInfo,
  1718  			op:      so,
  1719  		}
  1720  		fbo.unrefCache[ref] = si
  1721  	}
  1722  	return si, nil
  1723  }
  1724  
  1725  // GetDirtyFileBlockRefs returns a list of references of all known dirty
  1726  // files.
  1727  func (fbo *folderBlockOps) GetDirtyFileBlockRefs(
  1728  	lState *kbfssync.LockState) []data.BlockRef {
  1729  	fbo.blockLock.RLock(lState)
  1730  	defer fbo.blockLock.RUnlock(lState)
  1731  	var dirtyRefs []data.BlockRef
  1732  	for ref := range fbo.unrefCache {
  1733  		dirtyRefs = append(dirtyRefs, ref)
  1734  	}
  1735  	return dirtyRefs
  1736  }
  1737  
  1738  // GetDirtyDirBlockRefs returns a list of references of all known
  1739  // dirty directories.  Also returns a channel that, while it is open,
  1740  // all future writes will be blocked until it is closed -- this lets
  1741  // the caller ensure that the directory entries will remain stable
  1742  // (not updated with new file sizes by the writes) until all of the
  1743  // directory blocks have been safely copied.  The caller *must* close
  1744  // this channel once they are done processing the dirty directory
  1745  // blocks.
  1746  func (fbo *folderBlockOps) GetDirtyDirBlockRefs(
  1747  	lState *kbfssync.LockState) ([]data.BlockRef, chan<- struct{}) {
  1748  	fbo.blockLock.Lock(lState)
  1749  	defer fbo.blockLock.Unlock(lState)
  1750  	var dirtyRefs []data.BlockRef
  1751  	for ptr := range fbo.dirtyDirs {
  1752  		dirtyRefs = append(dirtyRefs, ptr.Ref())
  1753  	}
  1754  	if fbo.dirtyDirsSyncing {
  1755  		panic("GetDirtyDirBlockRefs() called twice")
  1756  	}
  1757  	fbo.dirtyDirsSyncing = true
  1758  	ch := make(chan struct{})
  1759  	fbo.holdNewWritesCh = ch
  1760  	return dirtyRefs, ch
  1761  }
  1762  
  1763  // GetDirtyDirBlockRefsDone is called to indicate the caller is done
  1764  // with the data previously returned from `GetDirtyDirBlockRefs()`.
  1765  func (fbo *folderBlockOps) GetDirtyDirBlockRefsDone(
  1766  	lState *kbfssync.LockState) {
  1767  	fbo.blockLock.Lock(lState)
  1768  	defer fbo.blockLock.Unlock(lState)
  1769  	fbo.dirtyDirsSyncing = false
  1770  	fbo.deferredDirUpdates = nil
  1771  	fbo.holdNewWritesCh = nil
  1772  }
  1773  
  1774  // getDirtyDirUnrefsLocked returns a list of block infos that need to be
  1775  // unreferenced for the given directory.
  1776  func (fbo *folderBlockOps) getDirtyDirUnrefsLocked(
  1777  	lState *kbfssync.LockState, ptr data.BlockPointer) []data.BlockInfo {
  1778  	fbo.blockLock.AssertRLocked(lState)
  1779  	return fbo.dirtyDirs[ptr]
  1780  }
  1781  
  1782  // fixChildBlocksAfterRecoverableErrorLocked should be called when a sync
  1783  // failed with a recoverable block error on a multi-block file.  It
  1784  // makes sure that any outstanding dirty versions of the file are
  1785  // fixed up to reflect the fact that some of the indirect pointers now
  1786  // need to change.
  1787  func (fbo *folderBlockOps) fixChildBlocksAfterRecoverableErrorLocked(
  1788  	ctx context.Context, lState *kbfssync.LockState, file data.Path, kmd libkey.KeyMetadata,
  1789  	redirtyOnRecoverableError map[data.BlockPointer]data.BlockPointer) {
  1790  	fbo.blockLock.AssertLocked(lState)
  1791  
  1792  	defer func() {
  1793  		// Below, this function can end up writing dirty blocks back
  1794  		// to the cache, which will set `doDeferWrite` to `true`.
  1795  		// This leads to future writes being unnecessarily deferred
  1796  		// when a Sync is not happening, and can lead to dirty data
  1797  		// being synced twice and sticking around for longer than
  1798  		// needed.  So just reset `doDeferWrite` once we're
  1799  		// done. We're under `blockLock`, so this is safe.
  1800  		fbo.doDeferWrite = false
  1801  	}()
  1802  
  1803  	df := fbo.dirtyFiles[file.TailPointer()]
  1804  	if df != nil {
  1805  		// Un-orphan old blocks, since we are reverting back to the
  1806  		// previous state.
  1807  		for _, oldPtr := range redirtyOnRecoverableError {
  1808  			fbo.vlog.CLogf(ctx, libkb.VLog1, "Un-orphaning %v", oldPtr)
  1809  			df.SetBlockOrphaned(oldPtr, false)
  1810  		}
  1811  	}
  1812  
  1813  	dirtyBcache := fbo.config.DirtyBlockCache()
  1814  	topBlock, err := dirtyBcache.Get(
  1815  		ctx, fbo.id(), file.TailPointer(), fbo.branch())
  1816  	fblock, ok := topBlock.(*data.FileBlock)
  1817  	if err != nil || !ok {
  1818  		fbo.log.CWarningf(ctx, "Couldn't find dirtied "+
  1819  			"top-block for %v: %v", file.TailPointer(), err)
  1820  		return
  1821  	}
  1822  
  1823  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  1824  	if err != nil {
  1825  		fbo.log.CWarningf(ctx, "Couldn't find uid during recovery: %v", err)
  1826  		return
  1827  	}
  1828  	fd := fbo.newFileData(lState, file, chargedTo, kmd)
  1829  
  1830  	// If a copy of the top indirect block was made, we need to
  1831  	// redirty all the sync'd blocks under their new IDs, so that
  1832  	// future syncs will know they failed.
  1833  	newPtrs := make(map[data.BlockPointer]bool, len(redirtyOnRecoverableError))
  1834  	for newPtr := range redirtyOnRecoverableError {
  1835  		newPtrs[newPtr] = true
  1836  	}
  1837  	found, err := fd.FindIPtrsAndClearSize(ctx, fblock, newPtrs)
  1838  	if err != nil {
  1839  		fbo.log.CWarningf(
  1840  			ctx, "Couldn't find and clear iptrs during recovery: %v", err)
  1841  		return
  1842  	}
  1843  	for newPtr, oldPtr := range redirtyOnRecoverableError {
  1844  		if !found[newPtr] {
  1845  			continue
  1846  		}
  1847  
  1848  		fbo.vlog.CLogf(
  1849  			ctx, libkb.VLog1, "Re-dirtying %v (and deleting dirty block %v)",
  1850  			newPtr, oldPtr)
  1851  		// These blocks would have been permanent, so they're
  1852  		// definitely still in the cache.
  1853  		b, err := fbo.config.BlockCache().Get(newPtr)
  1854  		if err != nil {
  1855  			fbo.log.CWarningf(ctx, "Couldn't re-dirty %v: %v", newPtr, err)
  1856  			continue
  1857  		}
  1858  		if err = fbo.cacheBlockIfNotYetDirtyLocked(
  1859  			ctx, lState, newPtr, file, b); err != nil {
  1860  			fbo.log.CWarningf(ctx, "Couldn't re-dirty %v: %v", newPtr, err)
  1861  		}
  1862  		fbo.vlog.CLogf(
  1863  			ctx, libkb.VLog1, "Deleting dirty ptr %v after recoverable error",
  1864  			oldPtr)
  1865  		err = dirtyBcache.Delete(fbo.id(), oldPtr, fbo.branch())
  1866  		if err != nil {
  1867  			fbo.vlog.CLogf(
  1868  				ctx, libkb.VLog1, "Couldn't del-dirty %v: %v", oldPtr, err)
  1869  		}
  1870  	}
  1871  }
  1872  
  1873  func (fbo *folderBlockOps) nowUnixNano() int64 {
  1874  	return fbo.config.Clock().Now().UnixNano()
  1875  }
  1876  
  1877  // PrepRename prepares the given rename operation. It returns the old
  1878  // and new parent block (which may be the same, and which shouldn't be
  1879  // modified), and what is to be the new DirEntry.
  1880  func (fbo *folderBlockOps) PrepRename(
  1881  	ctx context.Context, lState *kbfssync.LockState,
  1882  	kmd KeyMetadataWithRootDirEntry, oldParent data.Path,
  1883  	oldName data.PathPartString, newParent data.Path,
  1884  	newName data.PathPartString) (
  1885  	newDe, replacedDe data.DirEntry, ro *renameOp, err error) {
  1886  	fbo.blockLock.RLock(lState)
  1887  	defer fbo.blockLock.RUnlock(lState)
  1888  
  1889  	// Look up in the old path. Won't be modified, so only fetch for reading.
  1890  	newDe, err = fbo.getEntryLocked(
  1891  		ctx, lState, kmd, oldParent.ChildPathNoPtr(oldName, nil), false)
  1892  	if err != nil {
  1893  		return data.DirEntry{}, data.DirEntry{}, nil, err
  1894  	}
  1895  
  1896  	oldParentPtr := oldParent.TailPointer()
  1897  	newParentPtr := newParent.TailPointer()
  1898  	ro, err = newRenameOp(
  1899  		oldName.Plaintext(), oldParentPtr, newName.Plaintext(), newParentPtr,
  1900  		newDe.BlockPointer, newDe.Type)
  1901  	if err != nil {
  1902  		return data.DirEntry{}, data.DirEntry{}, nil, err
  1903  	}
  1904  	ro.AddUpdate(oldParentPtr, oldParentPtr)
  1905  	ro.setFinalPath(newParent)
  1906  	ro.oldFinalPath = oldParent
  1907  	if oldParentPtr.ID != newParentPtr.ID {
  1908  		ro.AddUpdate(newParentPtr, newParentPtr)
  1909  	}
  1910  
  1911  	replacedDe, err = fbo.getEntryLocked(
  1912  		ctx, lState, kmd, newParent.ChildPathNoPtr(newName, nil), false)
  1913  	if _, notExists := errors.Cause(err).(idutil.NoSuchNameError); notExists {
  1914  		return newDe, data.DirEntry{}, ro, nil
  1915  	} else if err != nil {
  1916  		return data.DirEntry{}, data.DirEntry{}, nil, err
  1917  	}
  1918  
  1919  	return newDe, replacedDe, ro, nil
  1920  }
  1921  
  1922  func (fbo *folderBlockOps) newFileData(lState *kbfssync.LockState,
  1923  	file data.Path, chargedTo keybase1.UserOrTeamID, kmd libkey.KeyMetadata) *data.FileData {
  1924  	fbo.blockLock.AssertAnyLocked(lState)
  1925  	return data.NewFileData(file, chargedTo, fbo.config.BlockSplitter(), kmd,
  1926  		func(ctx context.Context, kmd libkey.KeyMetadata, ptr data.BlockPointer,
  1927  			file data.Path, rtype data.BlockReqType) (*data.FileBlock, bool, error) {
  1928  			lState := lState
  1929  			if rtype == data.BlockReadParallel {
  1930  				lState = nil
  1931  			}
  1932  			return fbo.getFileBlockLocked(
  1933  				ctx, lState, kmd, ptr, file, rtype)
  1934  		},
  1935  		func(ctx context.Context, ptr data.BlockPointer, block data.Block) error {
  1936  			return fbo.cacheBlockIfNotYetDirtyLocked(
  1937  				ctx, lState, ptr, file, block)
  1938  		}, fbo.log, fbo.vlog)
  1939  }
  1940  
  1941  func (fbo *folderBlockOps) newFileDataWithCache(lState *kbfssync.LockState,
  1942  	file data.Path, chargedTo keybase1.UserOrTeamID, kmd libkey.KeyMetadata,
  1943  	dirtyBcache data.DirtyBlockCacheSimple) *data.FileData {
  1944  	fbo.blockLock.AssertAnyLocked(lState)
  1945  	return data.NewFileData(file, chargedTo, fbo.config.BlockSplitter(), kmd,
  1946  		func(ctx context.Context, kmd libkey.KeyMetadata, ptr data.BlockPointer,
  1947  			file data.Path, rtype data.BlockReqType) (*data.FileBlock, bool, error) {
  1948  			block, err := dirtyBcache.Get(ctx, file.Tlf, ptr, file.Branch)
  1949  			if fblock, ok := block.(*data.FileBlock); ok && err == nil {
  1950  				return fblock, true, nil
  1951  			}
  1952  			lState := lState
  1953  			if rtype == data.BlockReadParallel {
  1954  				lState = nil
  1955  			}
  1956  			return fbo.getFileBlockLocked(
  1957  				ctx, lState, kmd, ptr, file, rtype)
  1958  		},
  1959  		func(ctx context.Context, ptr data.BlockPointer, block data.Block) error {
  1960  			return dirtyBcache.Put(ctx, file.Tlf, ptr, file.Branch, block)
  1961  		}, fbo.log, fbo.vlog)
  1962  }
  1963  
  1964  // Read reads from the given file into the given buffer at the given
  1965  // offset. It returns the number of bytes read and nil, or 0 and the
  1966  // error if there was one.
  1967  func (fbo *folderBlockOps) Read(
  1968  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata, file Node,
  1969  	dest []byte, off int64) (int64, error) {
  1970  	fbo.blockLock.RLock(lState)
  1971  	defer fbo.blockLock.RUnlock(lState)
  1972  
  1973  	filePath := fbo.nodeCache.PathFromNode(file)
  1974  
  1975  	fbo.vlog.CLogf(ctx, libkb.VLog1, "Reading from %v", filePath.TailPointer())
  1976  
  1977  	var id keybase1.UserOrTeamID // Data reads don't depend on the id.
  1978  	fd := fbo.newFileData(lState, filePath, id, kmd)
  1979  	return fd.Read(ctx, dest, data.Int64Offset(off))
  1980  }
  1981  
  1982  func (fbo *folderBlockOps) maybeWaitOnDeferredWrites(
  1983  	ctx context.Context, lState *kbfssync.LockState, file Node,
  1984  	c data.DirtyPermChan) error {
  1985  	var errListener chan error
  1986  	registerErr := func() error {
  1987  		fbo.blockLock.Lock(lState)
  1988  		defer fbo.blockLock.Unlock(lState)
  1989  		filePath, err := fbo.pathFromNodeForBlockWriteLocked(lState, file)
  1990  		if err != nil {
  1991  			return err
  1992  		}
  1993  		df := fbo.getOrCreateDirtyFileLocked(lState, filePath)
  1994  		errListener = make(chan error, 1)
  1995  		df.AddErrListener(errListener)
  1996  		return nil
  1997  	}
  1998  	err := registerErr()
  1999  	if err != nil {
  2000  		return err
  2001  	}
  2002  
  2003  	logTimer := time.After(100 * time.Millisecond)
  2004  	doLogUnblocked := false
  2005  	for {
  2006  		var err error
  2007  	outerSelect:
  2008  		select {
  2009  		case <-c:
  2010  			if doLogUnblocked {
  2011  				fbo.vlog.CLogf(ctx, libkb.VLog1, "Write unblocked")
  2012  			}
  2013  			// Make sure there aren't any queued errors.
  2014  			select {
  2015  			case err = <-errListener:
  2016  				// Break the select to check the cause of the error below.
  2017  				break outerSelect
  2018  			default:
  2019  			}
  2020  			return nil
  2021  		case <-logTimer:
  2022  			// Print a log message once if it's taking too long.
  2023  			fbo.log.CDebugf(ctx,
  2024  				"Blocking a write because of a full dirty buffer")
  2025  			doLogUnblocked = true
  2026  		case <-ctx.Done():
  2027  			return ctx.Err()
  2028  		case err = <-errListener:
  2029  			// Fall through to check the cause of the error below.
  2030  		}
  2031  		// Context errors are safe to ignore, since they are likely to
  2032  		// be specific to a previous sync (e.g., a user hit ctrl-c
  2033  		// during an fsync, or a sync timed out, or a test was
  2034  		// provoking an error specifically [KBFS-2164]).
  2035  		cause := errors.Cause(err)
  2036  		if cause == context.Canceled || cause == context.DeadlineExceeded {
  2037  			fbo.vlog.CLogf(ctx, libkb.VLog1, "Ignoring sync err: %+v", err)
  2038  			err := registerErr()
  2039  			if err != nil {
  2040  				return err
  2041  			}
  2042  			continue
  2043  		} else if err != nil {
  2044  			// Treat other errors as fatal to this write -- e.g., the
  2045  			// user's quota is full, the local journal is broken,
  2046  			// etc. XXX: should we ignore errors that are specific
  2047  			// only to some other file being sync'd (e.g.,
  2048  			// "recoverable" block errors from which we couldn't
  2049  			// recover)?
  2050  			return err
  2051  		}
  2052  	}
  2053  }
  2054  
  2055  func (fbo *folderBlockOps) pathFromNodeForBlockWriteLocked(
  2056  	lState *kbfssync.LockState, n Node) (data.Path, error) {
  2057  	fbo.blockLock.AssertLocked(lState)
  2058  	p := fbo.nodeCache.PathFromNode(n)
  2059  	if !p.IsValid() {
  2060  		return data.Path{}, errors.WithStack(InvalidPathError{p})
  2061  	}
  2062  	return p, nil
  2063  }
  2064  
  2065  // writeGetFileLocked checks write permissions explicitly for
  2066  // writeDataLocked, truncateLocked etc and returns
  2067  func (fbo *folderBlockOps) writeGetFileLocked(
  2068  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata,
  2069  	file data.Path) (*data.FileBlock, error) {
  2070  	fbo.blockLock.AssertLocked(lState)
  2071  
  2072  	session, err := fbo.config.KBPKI().GetCurrentSession(ctx)
  2073  	if err != nil {
  2074  		return nil, err
  2075  	}
  2076  	isWriter, err := kmd.IsWriter(
  2077  		ctx, fbo.config.KBPKI(), fbo.config, session.UID, session.VerifyingKey)
  2078  	if err != nil {
  2079  		return nil, err
  2080  	}
  2081  	if !isWriter {
  2082  		return nil, tlfhandle.NewWriteAccessError(kmd.GetTlfHandle(),
  2083  			session.Name, file.String())
  2084  	}
  2085  	fblock, err := fbo.getFileLocked(ctx, lState, kmd, file, data.BlockWrite)
  2086  	if err != nil {
  2087  		return nil, err
  2088  	}
  2089  	return fblock, nil
  2090  }
  2091  
  2092  // Returns the set of blocks dirtied during this write that might need
  2093  // to be cleaned up if the write is deferred.
  2094  func (fbo *folderBlockOps) writeDataLocked(
  2095  	ctx context.Context, lState *kbfssync.LockState,
  2096  	kmd KeyMetadataWithRootDirEntry, file data.Path, buf []byte, off int64) (
  2097  	latestWrite WriteRange, dirtyPtrs []data.BlockPointer,
  2098  	newlyDirtiedChildBytes int64, err error) {
  2099  	_, wasAlreadyUnref := fbo.unrefCache[file.TailPointer().Ref()]
  2100  	defer func() {
  2101  		// if the write didn't succeed, and the file wasn't already
  2102  		// being cached, clear out any cached state.
  2103  		if err != nil && !wasAlreadyUnref {
  2104  			_ = fbo.clearCacheInfoLocked(lState, file)
  2105  		}
  2106  	}()
  2107  
  2108  	if jManager, err := GetJournalManager(fbo.config); err == nil {
  2109  		jManager.dirtyOpStart(fbo.id())
  2110  		defer jManager.dirtyOpEnd(fbo.id())
  2111  	}
  2112  
  2113  	fbo.blockLock.AssertLocked(lState)
  2114  	fbo.vlog.CLogf(ctx, libkb.VLog1, "writeDataLocked on file pointer %v",
  2115  		file.TailPointer())
  2116  	defer func() {
  2117  		fbo.vlog.CLogf(ctx, libkb.VLog1, "writeDataLocked done: %v", err)
  2118  	}()
  2119  
  2120  	fblock, err := fbo.writeGetFileLocked(ctx, lState, kmd, file)
  2121  	if err != nil {
  2122  		return WriteRange{}, nil, 0, err
  2123  	}
  2124  
  2125  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  2126  	if err != nil {
  2127  		return WriteRange{}, nil, 0, err
  2128  	}
  2129  
  2130  	fd := fbo.newFileData(lState, file, chargedTo, kmd)
  2131  
  2132  	dirtyBcache := fbo.config.DirtyBlockCache()
  2133  	df := fbo.getOrCreateDirtyFileLocked(lState, file)
  2134  	defer func() {
  2135  		// Always update unsynced bytes and potentially force a sync,
  2136  		// even on an error, since the previously-dirty bytes stay in
  2137  		// the cache.
  2138  		df.UpdateNotYetSyncingBytes(newlyDirtiedChildBytes)
  2139  		if dirtyBcache.ShouldForceSync(fbo.id()) {
  2140  			select {
  2141  			// If we can't send on the channel, that means a sync is
  2142  			// already in progress.
  2143  			case fbo.forceSyncChan <- struct{}{}:
  2144  				fbo.vlog.CLogf(
  2145  					ctx, libkb.VLog1, "Forcing a sync due to full buffer")
  2146  			default:
  2147  			}
  2148  		}
  2149  	}()
  2150  
  2151  	de, err := fbo.getEntryLocked(ctx, lState, kmd, file, true)
  2152  	if err != nil {
  2153  		return WriteRange{}, nil, 0, err
  2154  	}
  2155  	if de.BlockPointer != file.TailPointer() {
  2156  		fbo.log.CDebugf(ctx, "DirEntry and file tail pointer don't match: "+
  2157  			"%v vs %v, parent=%s", de.BlockPointer, file.TailPointer(),
  2158  			file.ParentPath().TailPointer())
  2159  	}
  2160  
  2161  	si, err := fbo.getOrCreateSyncInfoLocked(lState, de)
  2162  	if err != nil {
  2163  		return WriteRange{}, nil, 0, err
  2164  	}
  2165  
  2166  	newDe, dirtyPtrs, unrefs, newlyDirtiedChildBytes, bytesExtended, err :=
  2167  		fd.Write(ctx, buf, data.Int64Offset(off), fblock, de, df)
  2168  	// Record the unrefs before checking the error so we remember the
  2169  	// state of newly dirtied blocks.
  2170  	si.unrefs = append(si.unrefs, unrefs...)
  2171  	if err != nil {
  2172  		return WriteRange{}, nil, newlyDirtiedChildBytes, err
  2173  	}
  2174  
  2175  	// Update the file's directory entry.
  2176  	now := fbo.nowUnixNano()
  2177  	newDe.Mtime = now
  2178  	newDe.Ctime = now
  2179  	err = fbo.updateEntryLocked(ctx, lState, kmd, file, newDe, true)
  2180  	if err != nil {
  2181  		return WriteRange{}, nil, newlyDirtiedChildBytes, err
  2182  	}
  2183  
  2184  	if fbo.doDeferWrite {
  2185  		df.AddDeferredNewBytes(bytesExtended)
  2186  	}
  2187  
  2188  	latestWrite = si.op.addWrite(uint64(off), uint64(len(buf)))
  2189  
  2190  	return latestWrite, dirtyPtrs, newlyDirtiedChildBytes, nil
  2191  }
  2192  
  2193  func (fbo *folderBlockOps) holdWritesLocked(
  2194  	ctx context.Context, lState *kbfssync.LockState) error {
  2195  	fbo.blockLock.AssertLocked(lState)
  2196  
  2197  	// Loop until either the hold channel is nil, or it has been
  2198  	// closed.  However, we can't hold the lock while we're waiting
  2199  	// for it to close, as that will cause deadlocks.  So we need to
  2200  	// verify that it's the _same_ channel that was closed after we
  2201  	// re-take the lock; otherwise, we need to wait again on the new
  2202  	// channel.
  2203  	for fbo.holdNewWritesCh != nil {
  2204  		ch := fbo.holdNewWritesCh
  2205  		fbo.blockLock.Unlock(lState)
  2206  		fbo.vlog.CLogf(ctx, libkb.VLog1, "Blocking write on hold channel")
  2207  		select {
  2208  		case <-ch:
  2209  			fbo.blockLock.Lock(lState)
  2210  			// If the channel hasn't changed since we checked it
  2211  			// outside of the lock, we are good to proceed.
  2212  			if ch == fbo.holdNewWritesCh {
  2213  				fbo.vlog.CLogf(
  2214  					ctx, libkb.VLog1, "Unblocking write on hold channel")
  2215  				return nil
  2216  			}
  2217  		case <-ctx.Done():
  2218  			fbo.blockLock.Lock(lState)
  2219  			return ctx.Err()
  2220  		}
  2221  	}
  2222  	return nil
  2223  }
  2224  
  2225  // Write writes the given data to the given file. May block if there
  2226  // is too much unflushed data; in that case, it will be unblocked by a
  2227  // future sync.
  2228  func (fbo *folderBlockOps) Write(
  2229  	ctx context.Context, lState *kbfssync.LockState,
  2230  	kmd KeyMetadataWithRootDirEntry, file Node, buf []byte, off int64) error {
  2231  	// If there is too much unflushed data, we should wait until some
  2232  	// of it gets flush so our memory usage doesn't grow without
  2233  	// bound.
  2234  	c, err := fbo.config.DirtyBlockCache().RequestPermissionToDirty(ctx,
  2235  		fbo.id(), int64(len(buf)))
  2236  	if err != nil {
  2237  		return err
  2238  	}
  2239  	defer fbo.config.DirtyBlockCache().UpdateUnsyncedBytes(fbo.id(),
  2240  		-int64(len(buf)), false)
  2241  	err = fbo.maybeWaitOnDeferredWrites(ctx, lState, file, c)
  2242  	if err != nil {
  2243  		return err
  2244  	}
  2245  
  2246  	fbo.blockLock.Lock(lState)
  2247  	defer fbo.blockLock.Unlock(lState)
  2248  
  2249  	err = fbo.holdWritesLocked(ctx, lState)
  2250  	if err != nil {
  2251  		return err
  2252  	}
  2253  
  2254  	filePath, err := fbo.pathFromNodeForBlockWriteLocked(lState, file)
  2255  	if err != nil {
  2256  		return err
  2257  	}
  2258  
  2259  	defer func() {
  2260  		fbo.doDeferWrite = false
  2261  	}()
  2262  
  2263  	latestWrite, dirtyPtrs, newlyDirtiedChildBytes, err := fbo.writeDataLocked(
  2264  		ctx, lState, kmd, filePath, buf, off)
  2265  	if err != nil {
  2266  		return err
  2267  	}
  2268  
  2269  	fbo.observers.localChange(ctx, file, latestWrite)
  2270  
  2271  	if fbo.doDeferWrite {
  2272  		// There's an ongoing sync, and this write altered dirty
  2273  		// blocks that are in the process of syncing.  So, we have to
  2274  		// redo this write once the sync is complete, using the new
  2275  		// file path.
  2276  		//
  2277  		// There is probably a less terrible of doing this that
  2278  		// doesn't involve so much copying and rewriting, but this is
  2279  		// the most obviously correct way.
  2280  		bufCopy := make([]byte, len(buf))
  2281  		copy(bufCopy, buf)
  2282  		fbo.vlog.CLogf(
  2283  			ctx, libkb.VLog1, "Deferring a write to file %v off=%d len=%d",
  2284  			filePath.TailPointer(), off, len(buf))
  2285  		ds := fbo.deferred[filePath.TailRef()]
  2286  		ds.dirtyDeletes = append(ds.dirtyDeletes, dirtyPtrs...)
  2287  		ds.writes = append(ds.writes,
  2288  			func(ctx context.Context, lState *kbfssync.LockState,
  2289  				kmd KeyMetadataWithRootDirEntry, f data.Path) error {
  2290  				// We are about to re-dirty these bytes, so mark that
  2291  				// they will no longer be synced via the old file.
  2292  				df := fbo.getOrCreateDirtyFileLocked(lState, filePath)
  2293  				df.UpdateNotYetSyncingBytes(-newlyDirtiedChildBytes)
  2294  
  2295  				// Write the data again.  We know this won't be
  2296  				// deferred, so no need to check the new ptrs.
  2297  				_, _, _, err = fbo.writeDataLocked(
  2298  					ctx, lState, kmd, f, bufCopy, off)
  2299  				return err
  2300  			})
  2301  		ds.waitBytes += newlyDirtiedChildBytes
  2302  		fbo.deferred[filePath.TailRef()] = ds
  2303  	}
  2304  
  2305  	return nil
  2306  }
  2307  
  2308  // truncateExtendLocked is called by truncateLocked to extend a file and
  2309  // creates a hole.
  2310  func (fbo *folderBlockOps) truncateExtendLocked(
  2311  	ctx context.Context, lState *kbfssync.LockState,
  2312  	kmd KeyMetadataWithRootDirEntry, file data.Path, size uint64,
  2313  	parentBlocks []data.ParentBlockAndChildIndex) (
  2314  	WriteRange, []data.BlockPointer, error) {
  2315  	fblock, err := fbo.writeGetFileLocked(ctx, lState, kmd, file)
  2316  	if err != nil {
  2317  		return WriteRange{}, nil, err
  2318  	}
  2319  
  2320  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  2321  	if err != nil {
  2322  		return WriteRange{}, nil, err
  2323  	}
  2324  
  2325  	fd := fbo.newFileData(lState, file, chargedTo, kmd)
  2326  
  2327  	de, err := fbo.getEntryLocked(ctx, lState, kmd, file, true)
  2328  	if err != nil {
  2329  		return WriteRange{}, nil, err
  2330  	}
  2331  	df := fbo.getOrCreateDirtyFileLocked(lState, file)
  2332  	newDe, dirtyPtrs, err := fd.TruncateExtend(
  2333  		ctx, size, fblock, parentBlocks, de, df)
  2334  	if err != nil {
  2335  		return WriteRange{}, nil, err
  2336  	}
  2337  
  2338  	now := fbo.nowUnixNano()
  2339  	newDe.Mtime = now
  2340  	newDe.Ctime = now
  2341  	err = fbo.updateEntryLocked(ctx, lState, kmd, file, newDe, true)
  2342  	if err != nil {
  2343  		return WriteRange{}, nil, err
  2344  	}
  2345  
  2346  	si, err := fbo.getOrCreateSyncInfoLocked(lState, de)
  2347  	if err != nil {
  2348  		return WriteRange{}, nil, err
  2349  	}
  2350  	latestWrite := si.op.addTruncate(size)
  2351  
  2352  	if fbo.config.DirtyBlockCache().ShouldForceSync(fbo.id()) {
  2353  		select {
  2354  		// If we can't send on the channel, that means a sync is
  2355  		// already in progress
  2356  		case fbo.forceSyncChan <- struct{}{}:
  2357  			fbo.vlog.CLogf(
  2358  				ctx, libkb.VLog1, "Forcing a sync due to full buffer")
  2359  		default:
  2360  		}
  2361  	}
  2362  
  2363  	fbo.vlog.CLogf(ctx, libkb.VLog1, "truncateExtendLocked: done")
  2364  	return latestWrite, dirtyPtrs, nil
  2365  }
  2366  
  2367  // Returns the set of newly-ID'd blocks created during this truncate
  2368  // that might need to be cleaned up if the truncate is deferred.
  2369  func (fbo *folderBlockOps) truncateLocked(
  2370  	ctx context.Context, lState *kbfssync.LockState,
  2371  	kmd KeyMetadataWithRootDirEntry, file data.Path, size uint64) (
  2372  	wr *WriteRange, ptrs []data.BlockPointer, dirtyBytes int64, err error) {
  2373  	_, wasAlreadyUnref := fbo.unrefCache[file.TailPointer().Ref()]
  2374  	defer func() {
  2375  		// if the truncate didn't succeed, and the file wasn't already
  2376  		// being cached, clear out any cached state.
  2377  		if err != nil && !wasAlreadyUnref {
  2378  			_ = fbo.clearCacheInfoLocked(lState, file)
  2379  		}
  2380  	}()
  2381  
  2382  	if jManager, err := GetJournalManager(fbo.config); err == nil {
  2383  		jManager.dirtyOpStart(fbo.id())
  2384  		defer jManager.dirtyOpEnd(fbo.id())
  2385  	}
  2386  
  2387  	fblock, err := fbo.writeGetFileLocked(ctx, lState, kmd, file)
  2388  	if err != nil {
  2389  		return &WriteRange{}, nil, 0, err
  2390  	}
  2391  
  2392  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  2393  	if err != nil {
  2394  		return &WriteRange{}, nil, 0, err
  2395  	}
  2396  
  2397  	fd := fbo.newFileData(lState, file, chargedTo, kmd)
  2398  
  2399  	// find the block where the file should now end
  2400  	iSize := int64(size) // TODO: deal with overflow
  2401  	_, parentBlocks, block, nextBlockOff, startOff, _, err :=
  2402  		fd.GetFileBlockAtOffset(
  2403  			ctx, fblock, data.Int64Offset(iSize), data.BlockWrite)
  2404  	if err != nil {
  2405  		return &WriteRange{}, nil, 0, err
  2406  	}
  2407  
  2408  	currLen := int64(startOff) + int64(len(block.Contents))
  2409  	switch {
  2410  	case currLen+truncateExtendCutoffPoint < iSize:
  2411  		latestWrite, dirtyPtrs, err := fbo.truncateExtendLocked(
  2412  			ctx, lState, kmd, file, uint64(iSize), parentBlocks)
  2413  		if err != nil {
  2414  			return &latestWrite, dirtyPtrs, 0, err
  2415  		}
  2416  		return &latestWrite, dirtyPtrs, 0, err
  2417  	case currLen < iSize:
  2418  		moreNeeded := iSize - currLen
  2419  		latestWrite, dirtyPtrs, newlyDirtiedChildBytes, err :=
  2420  			fbo.writeDataLocked(
  2421  				ctx, lState, kmd, file, make([]byte, moreNeeded), currLen)
  2422  		if err != nil {
  2423  			return &latestWrite, dirtyPtrs, newlyDirtiedChildBytes, err
  2424  		}
  2425  		return &latestWrite, dirtyPtrs, newlyDirtiedChildBytes, err
  2426  	case currLen == iSize && nextBlockOff < 0:
  2427  		// same size!
  2428  		if !wasAlreadyUnref {
  2429  			_ = fbo.clearCacheInfoLocked(lState, file)
  2430  		}
  2431  		return nil, nil, 0, nil
  2432  	}
  2433  
  2434  	// update the local entry size
  2435  	de, err := fbo.getEntryLocked(ctx, lState, kmd, file, true)
  2436  	if err != nil {
  2437  		return nil, nil, 0, err
  2438  	}
  2439  
  2440  	si, err := fbo.getOrCreateSyncInfoLocked(lState, de)
  2441  	if err != nil {
  2442  		return nil, nil, 0, err
  2443  	}
  2444  
  2445  	newDe, dirtyPtrs, unrefs, newlyDirtiedChildBytes, err := fd.TruncateShrink(
  2446  		ctx, size, fblock, de)
  2447  	// Record the unrefs before checking the error so we remember the
  2448  	// state of newly dirtied blocks.
  2449  	si.unrefs = append(si.unrefs, unrefs...)
  2450  	if err != nil {
  2451  		return nil, nil, newlyDirtiedChildBytes, err
  2452  	}
  2453  
  2454  	// Update dirtied bytes and unrefs regardless of error.
  2455  	df := fbo.getOrCreateDirtyFileLocked(lState, file)
  2456  	df.UpdateNotYetSyncingBytes(newlyDirtiedChildBytes)
  2457  
  2458  	latestWrite := si.op.addTruncate(size)
  2459  	now := fbo.nowUnixNano()
  2460  	newDe.Mtime = now
  2461  	newDe.Ctime = now
  2462  	err = fbo.updateEntryLocked(ctx, lState, kmd, file, newDe, true)
  2463  	if err != nil {
  2464  		return nil, nil, newlyDirtiedChildBytes, err
  2465  	}
  2466  
  2467  	return &latestWrite, dirtyPtrs, newlyDirtiedChildBytes, nil
  2468  }
  2469  
  2470  // Truncate truncates or extends the given file to the given size.
  2471  // May block if there is too much unflushed data; in that case, it
  2472  // will be unblocked by a future sync.
  2473  func (fbo *folderBlockOps) Truncate(
  2474  	ctx context.Context, lState *kbfssync.LockState,
  2475  	kmd KeyMetadataWithRootDirEntry, file Node, size uint64) error {
  2476  	// If there is too much unflushed data, we should wait until some
  2477  	// of it gets flush so our memory usage doesn't grow without
  2478  	// bound.
  2479  	//
  2480  	// Assume the whole remaining file will be dirty after this
  2481  	// truncate.  TODO: try to figure out how many bytes actually will
  2482  	// be dirtied ahead of time?
  2483  	c, err := fbo.config.DirtyBlockCache().RequestPermissionToDirty(ctx,
  2484  		fbo.id(), int64(size))
  2485  	if err != nil {
  2486  		return err
  2487  	}
  2488  	defer fbo.config.DirtyBlockCache().UpdateUnsyncedBytes(fbo.id(),
  2489  		-int64(size), false)
  2490  	err = fbo.maybeWaitOnDeferredWrites(ctx, lState, file, c)
  2491  	if err != nil {
  2492  		return err
  2493  	}
  2494  
  2495  	fbo.blockLock.Lock(lState)
  2496  	defer fbo.blockLock.Unlock(lState)
  2497  
  2498  	err = fbo.holdWritesLocked(ctx, lState)
  2499  	if err != nil {
  2500  		return err
  2501  	}
  2502  
  2503  	filePath, err := fbo.pathFromNodeForBlockWriteLocked(lState, file)
  2504  	if err != nil {
  2505  		return err
  2506  	}
  2507  
  2508  	defer func() {
  2509  		fbo.doDeferWrite = false
  2510  	}()
  2511  
  2512  	latestWrite, dirtyPtrs, newlyDirtiedChildBytes, err := fbo.truncateLocked(
  2513  		ctx, lState, kmd, filePath, size)
  2514  	if err != nil {
  2515  		return err
  2516  	}
  2517  
  2518  	if latestWrite != nil {
  2519  		fbo.observers.localChange(ctx, file, *latestWrite)
  2520  	}
  2521  
  2522  	if fbo.doDeferWrite {
  2523  		// There's an ongoing sync, and this truncate altered
  2524  		// dirty blocks that are in the process of syncing.  So,
  2525  		// we have to redo this truncate once the sync is complete,
  2526  		// using the new file path.
  2527  		fbo.vlog.CLogf(
  2528  			ctx, libkb.VLog1, "Deferring a truncate to file %v",
  2529  			filePath.TailPointer())
  2530  		ds := fbo.deferred[filePath.TailRef()]
  2531  		ds.dirtyDeletes = append(ds.dirtyDeletes, dirtyPtrs...)
  2532  		ds.writes = append(ds.writes,
  2533  			func(ctx context.Context, lState *kbfssync.LockState,
  2534  				kmd KeyMetadataWithRootDirEntry, f data.Path) error {
  2535  				// We are about to re-dirty these bytes, so mark that
  2536  				// they will no longer be synced via the old file.
  2537  				df := fbo.getOrCreateDirtyFileLocked(lState, filePath)
  2538  				df.UpdateNotYetSyncingBytes(-newlyDirtiedChildBytes)
  2539  
  2540  				// Truncate the file again.  We know this won't be
  2541  				// deferred, so no need to check the new ptrs.
  2542  				_, _, _, err := fbo.truncateLocked(
  2543  					ctx, lState, kmd, f, size)
  2544  				return err
  2545  			})
  2546  		ds.waitBytes += newlyDirtiedChildBytes
  2547  		fbo.deferred[filePath.TailRef()] = ds
  2548  	}
  2549  
  2550  	return nil
  2551  }
  2552  
  2553  // IsDirty returns whether the given file is dirty; if false is
  2554  // returned, then the file doesn't need to be synced.
  2555  func (fbo *folderBlockOps) IsDirty(lState *kbfssync.LockState, file data.Path) bool {
  2556  	fbo.blockLock.RLock(lState)
  2557  	defer fbo.blockLock.RUnlock(lState)
  2558  	// A dirty file should probably match all three of these, but
  2559  	// check them individually just in case.
  2560  	if fbo.config.DirtyBlockCache().IsDirty(
  2561  		fbo.id(), file.TailPointer(), file.Branch) {
  2562  		return true
  2563  	}
  2564  
  2565  	if _, ok := fbo.dirtyFiles[file.TailPointer()]; ok {
  2566  		return ok
  2567  	}
  2568  
  2569  	_, ok := fbo.unrefCache[file.TailRef()]
  2570  	return ok
  2571  }
  2572  
  2573  func (fbo *folderBlockOps) clearCacheInfoLocked(lState *kbfssync.LockState,
  2574  	file data.Path) error {
  2575  	fbo.blockLock.AssertLocked(lState)
  2576  	ref := file.TailRef()
  2577  	delete(fbo.unrefCache, ref)
  2578  	df := fbo.dirtyFiles[file.TailPointer()]
  2579  	if df != nil {
  2580  		err := df.FinishSync()
  2581  		if err != nil {
  2582  			return err
  2583  		}
  2584  		delete(fbo.dirtyFiles, file.TailPointer())
  2585  	}
  2586  	return nil
  2587  }
  2588  
  2589  func (fbo *folderBlockOps) clearAllDirtyDirsLocked(
  2590  	ctx context.Context, lState *kbfssync.LockState, kmd libkey.KeyMetadata) {
  2591  	fbo.blockLock.AssertLocked(lState)
  2592  	dirtyBCache := fbo.config.DirtyBlockCache()
  2593  	for ptr := range fbo.dirtyDirs {
  2594  		dir := data.Path{
  2595  			FolderBranch: fbo.folderBranch,
  2596  			Path: []data.PathNode{
  2597  				{BlockPointer: ptr,
  2598  					Name: data.NewPathPartString(ptr.String(), nil),
  2599  				},
  2600  			},
  2601  		}
  2602  		dd := fbo.newDirDataLocked(lState, dir, keybase1.UserOrTeamID(""), kmd)
  2603  		childPtrs, err := dd.GetDirtyChildPtrs(ctx, dirtyBCache)
  2604  		if err != nil {
  2605  			fbo.log.CDebugf(ctx, "Failed to get child ptrs for %v: %+v",
  2606  				ptr, err)
  2607  		}
  2608  		for childPtr := range childPtrs {
  2609  			err := dirtyBCache.Delete(fbo.id(), childPtr, fbo.branch())
  2610  			if err != nil {
  2611  				fbo.log.CDebugf(
  2612  					ctx, "Failed to delete %v from dirty "+"cache: %+v",
  2613  					childPtr, err)
  2614  			}
  2615  		}
  2616  
  2617  		err = dirtyBCache.Delete(fbo.id(), ptr, fbo.branch())
  2618  		if err != nil {
  2619  			fbo.log.CDebugf(ctx, "Failed to delete %v from dirty cache: %+v",
  2620  				ptr, err)
  2621  		}
  2622  	}
  2623  	fbo.dirtyDirs = make(map[data.BlockPointer][]data.BlockInfo)
  2624  	fbo.dirtyRootDirEntry = nil
  2625  	fbo.dirtyDirsSyncing = false
  2626  	deferredDirUpdates := fbo.deferredDirUpdates
  2627  	fbo.deferredDirUpdates = nil
  2628  	// Re-apply any deferred directory updates related to files that
  2629  	// weren't synced as part of this batch.
  2630  	for _, f := range deferredDirUpdates {
  2631  		err := f(lState)
  2632  		if err != nil {
  2633  			fbo.log.CWarningf(ctx, "Deferred entry update failed: %+v", err)
  2634  		}
  2635  	}
  2636  }
  2637  
  2638  // ClearCacheInfo removes any cached info for the the given file.
  2639  func (fbo *folderBlockOps) ClearCacheInfo(
  2640  	lState *kbfssync.LockState, file data.Path) error {
  2641  	fbo.blockLock.Lock(lState)
  2642  	defer fbo.blockLock.Unlock(lState)
  2643  	return fbo.clearCacheInfoLocked(lState, file)
  2644  }
  2645  
  2646  // revertSyncInfoAfterRecoverableError updates the saved sync info to
  2647  // include all the blocks from before the error, except for those that
  2648  // have encountered recoverable block errors themselves.
  2649  func (fbo *folderBlockOps) revertSyncInfoAfterRecoverableError(
  2650  	ctx context.Context, blocksToRemove []data.BlockPointer, result fileSyncState) {
  2651  	si := result.si
  2652  	savedSi := result.savedSi
  2653  
  2654  	// Save the blocks we need to clean up on the next attempt.
  2655  	toClean := si.toCleanIfUnused
  2656  
  2657  	newIndirect := make(map[data.BlockPointer]bool)
  2658  	for _, ptr := range result.newIndirectFileBlockPtrs {
  2659  		newIndirect[ptr] = true
  2660  	}
  2661  
  2662  	// Propagate all unrefs forward, except those that belong to new
  2663  	// blocks that were created during the sync.
  2664  	unrefs := make([]data.BlockInfo, 0, len(si.unrefs))
  2665  	for _, unref := range si.unrefs {
  2666  		if newIndirect[unref.BlockPointer] {
  2667  			fbo.vlog.CLogf(ctx, libkb.VLog1, "Dropping unref %v", unref)
  2668  			continue
  2669  		}
  2670  		unrefs = append(unrefs, unref)
  2671  	}
  2672  
  2673  	// This sync will be retried and needs new blocks, so
  2674  	// reset everything in the sync info.
  2675  	*si = *savedSi
  2676  	si.toCleanIfUnused = toClean
  2677  	si.unrefs = unrefs
  2678  	if si.bps == nil {
  2679  		return
  2680  	}
  2681  
  2682  	// Mark any bad pointers so they get skipped next time.
  2683  	blocksToRemoveSet := make(map[data.BlockPointer]bool)
  2684  	for _, ptr := range blocksToRemove {
  2685  		blocksToRemoveSet[ptr] = true
  2686  	}
  2687  
  2688  	newBps, err := savedSi.bps.deepCopyWithBlacklist(ctx, blocksToRemoveSet)
  2689  	if err != nil {
  2690  		return
  2691  	}
  2692  	si.bps = newBps
  2693  }
  2694  
  2695  // fileSyncState holds state for a sync operation for a single
  2696  // file.
  2697  type fileSyncState struct {
  2698  	// If fblock is non-nil, the (dirty, indirect, cached) block
  2699  	// it points to will be set to savedFblock on a recoverable
  2700  	// error.
  2701  	fblock, savedFblock *data.FileBlock
  2702  
  2703  	// redirtyOnRecoverableError, which is non-nil only when fblock is
  2704  	// non-nil, contains pointers that need to be re-dirtied if the
  2705  	// top block gets copied during the sync, and a recoverable error
  2706  	// happens.  Maps to the old block pointer for the block, which
  2707  	// would need a DirtyBlockCache.Delete.
  2708  	redirtyOnRecoverableError map[data.BlockPointer]data.BlockPointer
  2709  
  2710  	// If si is non-nil, its updated state will be reset on
  2711  	// error. Also, if the error is recoverable, it will be
  2712  	// reverted to savedSi.
  2713  	//
  2714  	// TODO: Working with si in this way is racy, since si is a
  2715  	// member of unrefCache.
  2716  	si, savedSi *syncInfo
  2717  
  2718  	// oldFileBlockPtrs is a list of transient entries in the
  2719  	// block cache for the file, which should be removed when the
  2720  	// sync finishes.
  2721  	oldFileBlockPtrs []data.BlockPointer
  2722  
  2723  	// newIndirectFileBlockPtrs is a list of permanent entries
  2724  	// added to the block cache for the file, which should be
  2725  	// removed after the blocks have been sent to the server.
  2726  	// They are not removed on an error, because in that case the
  2727  	// file is still dirty locally and may get another chance to
  2728  	// be sync'd.
  2729  	//
  2730  	// TODO: This can be a list of IDs instead.
  2731  	newIndirectFileBlockPtrs []data.BlockPointer
  2732  }
  2733  
  2734  // startSyncWrite contains the portion of StartSync() that's done
  2735  // while write-locking blockLock.  If there is no dirty de cache
  2736  // entry, dirtyDe will be nil.
  2737  func (fbo *folderBlockOps) startSyncWrite(ctx context.Context,
  2738  	lState *kbfssync.LockState, md *RootMetadata, file data.Path) (
  2739  	fblock *data.FileBlock, bps blockPutStateCopiable, syncState fileSyncState,
  2740  	dirtyDe *data.DirEntry, err error) {
  2741  	fbo.blockLock.Lock(lState)
  2742  	defer fbo.blockLock.Unlock(lState)
  2743  
  2744  	// update the parent directories, and write all the new blocks out
  2745  	// to disk
  2746  	fblock, err = fbo.getFileLocked(ctx, lState, md.ReadOnly(), file, data.BlockWrite)
  2747  	if err != nil {
  2748  		return nil, nil, syncState, nil, err
  2749  	}
  2750  
  2751  	fileRef := file.TailRef()
  2752  	si, ok := fbo.unrefCache[fileRef]
  2753  	if !ok {
  2754  		return nil, nil, syncState, nil,
  2755  			fmt.Errorf("No syncOp found for file ref %v", fileRef)
  2756  	}
  2757  
  2758  	// Collapse the write range to reduce the size of the sync op.
  2759  	si.op.Writes = si.op.collapseWriteRange(nil)
  2760  	// If this function returns a success, we need to make sure the op
  2761  	// in `md` is not the same variable as the op in `unrefCache`,
  2762  	// because the latter could get updated still by local writes
  2763  	// before `md` is flushed to the server.  We don't copy it here
  2764  	// because code below still needs to modify it (and by extension,
  2765  	// the one stored in `syncState.si`).
  2766  	si.op.setFinalPath(file)
  2767  	md.AddOp(si.op)
  2768  
  2769  	// Fill in syncState.
  2770  	if fblock.IsInd {
  2771  		fblockCopy := fblock.DeepCopy()
  2772  		syncState.fblock = fblock
  2773  		syncState.savedFblock = fblockCopy
  2774  		syncState.redirtyOnRecoverableError = make(map[data.BlockPointer]data.BlockPointer)
  2775  	}
  2776  	syncState.si = si
  2777  	syncState.savedSi, err = si.DeepCopy(ctx, fbo.config.Codec())
  2778  	if err != nil {
  2779  		return nil, nil, syncState, nil, err
  2780  	}
  2781  
  2782  	if si.bps == nil {
  2783  		si.bps = newBlockPutStateMemory(1)
  2784  	} else {
  2785  		// reinstate byte accounting from the previous Sync
  2786  		md.SetRefBytes(si.refBytes)
  2787  		md.AddDiskUsage(si.refBytes)
  2788  		md.SetUnrefBytes(si.unrefBytes)
  2789  		md.SetMDRefBytes(0) // this will be calculated anew
  2790  		md.SetDiskUsage(md.DiskUsage() - si.unrefBytes)
  2791  		syncState.newIndirectFileBlockPtrs = append(
  2792  			syncState.newIndirectFileBlockPtrs, si.op.Refs()...)
  2793  	}
  2794  	defer func() {
  2795  		si.refBytes = md.RefBytes()
  2796  		si.unrefBytes = md.UnrefBytes()
  2797  	}()
  2798  
  2799  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, md)
  2800  	if err != nil {
  2801  		return nil, nil, syncState, nil, err
  2802  	}
  2803  
  2804  	dirtyBcache := fbo.config.DirtyBlockCache()
  2805  	df := fbo.getOrCreateDirtyFileLocked(lState, file)
  2806  	fd := fbo.newFileData(lState, file, chargedTo, md.ReadOnly())
  2807  
  2808  	// Note: below we add possibly updated file blocks as "unref" and
  2809  	// "ref" blocks.  This is fine, since conflict resolution or
  2810  	// notifications will never happen within a file.
  2811  
  2812  	// If needed, split the children blocks up along new boundaries
  2813  	// (e.g., if using a fingerprint-based block splitter).
  2814  	unrefs, err := fd.Split(ctx, fbo.id(), dirtyBcache, fblock, df)
  2815  	// Preserve any unrefs before checking the error.
  2816  	for _, unref := range unrefs {
  2817  		md.AddUnrefBlock(unref)
  2818  	}
  2819  	if err != nil {
  2820  		return nil, nil, syncState, nil, err
  2821  	}
  2822  
  2823  	// Ready all children blocks, if any.
  2824  	oldPtrs, err := fd.Ready(ctx, fbo.id(), fbo.config.BlockCache(),
  2825  		fbo.config.DirtyBlockCache(), fbo.config.BlockOps(), si.bps, fblock, df,
  2826  		fbo.cacheHashBehavior())
  2827  	if err != nil {
  2828  		return nil, nil, syncState, nil, err
  2829  	}
  2830  
  2831  	for newInfo, oldPtr := range oldPtrs {
  2832  		syncState.newIndirectFileBlockPtrs = append(
  2833  			syncState.newIndirectFileBlockPtrs, newInfo.BlockPointer)
  2834  		df.SetBlockOrphaned(oldPtr, true)
  2835  
  2836  		// Defer the DirtyBlockCache.Delete until after the new path
  2837  		// is ready, in case anyone tries to read the dirty file in
  2838  		// the meantime.
  2839  		syncState.oldFileBlockPtrs = append(syncState.oldFileBlockPtrs, oldPtr)
  2840  
  2841  		md.AddRefBlock(newInfo)
  2842  
  2843  		// If this block is replacing a block from a previous, failed
  2844  		// Sync, we need to take that block out of the refs list, and
  2845  		// avoid unrefing it as well.
  2846  		si.removeReplacedBlock(ctx, fbo.log, oldPtr)
  2847  
  2848  		err = df.SetBlockSyncing(ctx, oldPtr)
  2849  		if err != nil {
  2850  			return nil, nil, syncState, nil, err
  2851  		}
  2852  		syncState.redirtyOnRecoverableError[newInfo.BlockPointer] = oldPtr
  2853  	}
  2854  
  2855  	err = df.SetBlockSyncing(ctx, file.TailPointer())
  2856  	if err != nil {
  2857  		return nil, nil, syncState, nil, err
  2858  	}
  2859  	syncState.oldFileBlockPtrs = append(
  2860  		syncState.oldFileBlockPtrs, file.TailPointer())
  2861  
  2862  	// Capture the current de before we release the block lock, so
  2863  	// other deferred writes don't slip in.
  2864  	dd := fbo.newDirDataLocked(lState, *file.ParentPath(), chargedTo, md)
  2865  	de, err := dd.Lookup(ctx, file.TailName())
  2866  	if err != nil {
  2867  		return nil, nil, syncState, nil, err
  2868  	}
  2869  	dirtyDe = &de
  2870  
  2871  	// Leave a copy of the syncOp in `unrefCache`, since it may be
  2872  	// modified by future local writes while the syncOp in `md` should
  2873  	// only be modified by the rest of this sync process.
  2874  	var syncOpCopy *syncOp
  2875  	err = kbfscodec.Update(fbo.config.Codec(), &syncOpCopy, si.op)
  2876  	if err != nil {
  2877  		return nil, nil, syncState, nil, err
  2878  	}
  2879  	fbo.unrefCache[fileRef].op = syncOpCopy
  2880  
  2881  	// If there are any deferred bytes, it must be because this is
  2882  	// a retried sync and some blocks snuck in between sync. Those
  2883  	// blocks will get transferred now, but they are also on the
  2884  	// deferred list and will be retried on the next sync as well.
  2885  	df.AssimilateDeferredNewBytes()
  2886  
  2887  	// TODO: Returning si.bps in this way is racy, since si is a
  2888  	// member of unrefCache.
  2889  	return fblock, si.bps, syncState, dirtyDe, nil
  2890  }
  2891  
  2892  func prepDirtyEntryForSync(md *RootMetadata, si *syncInfo, dirtyDe *data.DirEntry) {
  2893  	// Add in the cached unref'd blocks.
  2894  	si.mergeUnrefCache(md)
  2895  	// Update the file's directory entry to the cached copy.
  2896  	if dirtyDe != nil {
  2897  		dirtyDe.EncodedSize = si.oldInfo.EncodedSize
  2898  	}
  2899  }
  2900  
  2901  // mergeDirtyEntryWithDBM sets the entry for a file into a directory,
  2902  // storing all the affected blocks into `dbm` rather than the dirty
  2903  // block cache.  It must only be called with an entry that's already
  2904  // been written to the dirty block cache, such that no new blocks are
  2905  // dirtied.
  2906  func (fbo *folderBlockOps) mergeDirtyEntryWithDBM(
  2907  	ctx context.Context, lState *kbfssync.LockState, file data.Path, md libkey.KeyMetadata,
  2908  	dbm dirBlockMap, dirtyDe data.DirEntry) error {
  2909  	// Lock and fetch for reading only, any dirty blocks will go into
  2910  	// the dbm.
  2911  	fbo.blockLock.RLock(lState)
  2912  	defer fbo.blockLock.RUnlock(lState)
  2913  
  2914  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, md)
  2915  	if err != nil {
  2916  		return err
  2917  	}
  2918  
  2919  	dd := fbo.newDirDataWithDBMLocked(
  2920  		lState, *file.ParentPath(), chargedTo, md, dbm)
  2921  	unrefs, err := dd.SetEntry(ctx, file.TailName(), dirtyDe)
  2922  	if err != nil {
  2923  		return err
  2924  	}
  2925  	if len(unrefs) != 0 {
  2926  		return errors.Errorf(
  2927  			"Merging dirty entry produced %d new unrefs", len(unrefs))
  2928  	}
  2929  	return nil
  2930  }
  2931  
  2932  // StartSync starts a sync for the given file. It returns the new
  2933  // FileBlock which has the readied top-level block which includes all
  2934  // writes since the last sync. Must be used with CleanupSyncState()
  2935  // and UpdatePointers/FinishSyncLocked() like so:
  2936  //
  2937  //		fblock, bps, dirtyDe, syncState, err :=
  2938  //			...fbo.StartSync(ctx, lState, md, uid, file)
  2939  //		defer func() {
  2940  //			...fbo.CleanupSyncState(
  2941  //				ctx, lState, md, file, ..., syncState, err)
  2942  //		}()
  2943  //		if err != nil {
  2944  //			...
  2945  //		}
  2946  //	     ...
  2947  //
  2948  //
  2949  //		... = fbo.UpdatePointers(..., func() error {
  2950  //	     ...fbo.FinishSyncLocked(ctx, lState, file, ..., syncState)
  2951  //	 })
  2952  func (fbo *folderBlockOps) StartSync(ctx context.Context,
  2953  	lState *kbfssync.LockState, md *RootMetadata, file data.Path) (
  2954  	fblock *data.FileBlock, bps blockPutStateCopiable, dirtyDe *data.DirEntry,
  2955  	syncState fileSyncState, err error) {
  2956  	if jManager, err := GetJournalManager(fbo.config); err == nil {
  2957  		jManager.dirtyOpStart(fbo.id())
  2958  	}
  2959  
  2960  	fblock, bps, syncState, dirtyDe, err = fbo.startSyncWrite(
  2961  		ctx, lState, md, file)
  2962  	if err != nil {
  2963  		return nil, nil, nil, syncState, err
  2964  	}
  2965  
  2966  	prepDirtyEntryForSync(md, syncState.si, dirtyDe)
  2967  	return fblock, bps, dirtyDe, syncState, err
  2968  }
  2969  
  2970  // Does any clean-up for a sync of the given file, given an error
  2971  // (which may be nil) that happens during or after StartSync() and
  2972  // before FinishSync(). blocksToRemove may be nil.
  2973  func (fbo *folderBlockOps) CleanupSyncState(
  2974  	ctx context.Context, lState *kbfssync.LockState, md ReadOnlyRootMetadata,
  2975  	file data.Path, blocksToRemove []data.BlockPointer,
  2976  	result fileSyncState, err error) {
  2977  	if jManager, err := GetJournalManager(fbo.config); err == nil {
  2978  		defer jManager.dirtyOpEnd(fbo.id())
  2979  	}
  2980  
  2981  	if err == nil {
  2982  		return
  2983  	}
  2984  
  2985  	fbo.blockLock.Lock(lState)
  2986  	defer fbo.blockLock.Unlock(lState)
  2987  
  2988  	// Notify error listeners before we reset the dirty blocks and
  2989  	// permissions to be granted.
  2990  	fbo.notifyErrListenersLocked(lState, file.TailPointer(), err)
  2991  
  2992  	// If there was an error, we need to back out any changes that
  2993  	// might have been filled into the sync op, because it could
  2994  	// get reused again in a later Sync call.
  2995  	if result.si != nil {
  2996  		result.si.op.resetUpdateState()
  2997  
  2998  		// Save this MD for later, so we can clean up its
  2999  		// newly-referenced block pointers if necessary.
  3000  		bpsCopy, err := result.si.bps.deepCopy(ctx)
  3001  		if err != nil {
  3002  			return
  3003  		}
  3004  		result.si.toCleanIfUnused = append(result.si.toCleanIfUnused,
  3005  			mdToCleanIfUnused{md, bpsCopy})
  3006  	}
  3007  	if isRecoverableBlockError(err) {
  3008  		if result.si != nil {
  3009  			fbo.revertSyncInfoAfterRecoverableError(ctx, blocksToRemove, result)
  3010  		}
  3011  		if result.fblock != nil {
  3012  			result.fblock.Set(result.savedFblock)
  3013  			fbo.fixChildBlocksAfterRecoverableErrorLocked(
  3014  				ctx, lState, file, md,
  3015  				result.redirtyOnRecoverableError)
  3016  		}
  3017  	} else {
  3018  		// Since the sync has errored out unrecoverably, the deferred
  3019  		// bytes are already accounted for.
  3020  		ds := fbo.deferred[file.TailRef()]
  3021  		if df := fbo.dirtyFiles[file.TailPointer()]; df != nil {
  3022  			df.UpdateNotYetSyncingBytes(-ds.waitBytes)
  3023  
  3024  			// Some blocks that were dirty are now clean under their
  3025  			// readied block ID, and now live in the bps rather than
  3026  			// the dirty bcache, so we can delete them from the dirty
  3027  			// bcache.
  3028  			dirtyBcache := fbo.config.DirtyBlockCache()
  3029  			for _, ptr := range result.oldFileBlockPtrs {
  3030  				if df.IsBlockOrphaned(ptr) {
  3031  					fbo.vlog.CLogf(
  3032  						ctx, libkb.VLog1, "Deleting dirty orphan: %v", ptr)
  3033  					if err := dirtyBcache.Delete(fbo.id(), ptr,
  3034  						fbo.branch()); err != nil {
  3035  						fbo.vlog.CLogf(
  3036  							ctx, libkb.VLog1, "Couldn't delete %v", ptr)
  3037  					}
  3038  				}
  3039  			}
  3040  		}
  3041  
  3042  		// On an unrecoverable error, the deferred writes aren't
  3043  		// needed anymore since they're already part of the
  3044  		// (still-)dirty blocks.
  3045  		delete(fbo.deferred, file.TailRef())
  3046  	}
  3047  
  3048  	// The sync is over, due to an error, so reset the map so that we
  3049  	// don't defer any subsequent writes.
  3050  	// Old syncing blocks are now just dirty
  3051  	if df := fbo.dirtyFiles[file.TailPointer()]; df != nil {
  3052  		df.ResetSyncingBlocksToDirty()
  3053  	}
  3054  }
  3055  
  3056  // cleanUpUnusedBlocks cleans up the blocks from any previous failed
  3057  // sync attempts.
  3058  func (fbo *folderBlockOps) cleanUpUnusedBlocks(ctx context.Context,
  3059  	md ReadOnlyRootMetadata, syncState fileSyncState, fbm *folderBlockManager) error {
  3060  	numToClean := len(syncState.si.toCleanIfUnused)
  3061  	if numToClean == 0 {
  3062  		return nil
  3063  	}
  3064  
  3065  	// What blocks are referenced in the successful MD?
  3066  	refs := make(map[data.BlockPointer]bool)
  3067  	for _, op := range md.data.Changes.Ops {
  3068  		for _, ptr := range op.Refs() {
  3069  			if ptr == data.ZeroPtr {
  3070  				panic("Unexpected zero ref ptr in a sync MD revision")
  3071  			}
  3072  			refs[ptr] = true
  3073  		}
  3074  		for _, update := range op.allUpdates() {
  3075  			if update.Ref == data.ZeroPtr {
  3076  				panic("Unexpected zero update ref ptr in a sync MD revision")
  3077  			}
  3078  
  3079  			refs[update.Ref] = true
  3080  		}
  3081  	}
  3082  
  3083  	// For each MD to clean, clean up the old failed blocks
  3084  	// immediately if the merge status matches the successful put, if
  3085  	// they didn't get referenced in the successful put.  If the merge
  3086  	// status is different (e.g., we ended up on a conflict branch),
  3087  	// clean it up only if the original revision failed.  If the same
  3088  	// block appears more than once, the one with a different merged
  3089  	// status takes precedence (which will always come earlier in the
  3090  	// list of MDs).
  3091  	blocksSeen := make(map[data.BlockPointer]bool)
  3092  	for _, oldMD := range syncState.si.toCleanIfUnused {
  3093  		bdType := blockDeleteAlways
  3094  		if oldMD.md.MergedStatus() != md.MergedStatus() {
  3095  			bdType = blockDeleteOnMDFail
  3096  		}
  3097  
  3098  		failedBps := newBlockPutStateMemory(oldMD.bps.numBlocks())
  3099  		for _, ptr := range oldMD.bps.Ptrs() {
  3100  			if ptr == data.ZeroPtr {
  3101  				panic("Unexpected zero block ptr in an old sync MD revision")
  3102  			}
  3103  			if blocksSeen[ptr] {
  3104  				continue
  3105  			}
  3106  			blocksSeen[ptr] = true
  3107  			if refs[ptr] && bdType == blockDeleteAlways {
  3108  				continue
  3109  			}
  3110  			failedBps.blockStates[ptr] = blockState{}
  3111  			fbo.vlog.CLogf(
  3112  				ctx, libkb.VLog1, "Cleaning up block %v from a previous "+
  3113  					"failed revision %d (oldMD is %s, bdType=%d)", ptr,
  3114  				oldMD.md.Revision(), oldMD.md.MergedStatus(), bdType)
  3115  		}
  3116  
  3117  		if len(failedBps.blockStates) > 0 {
  3118  			fbm.cleanUpBlockState(oldMD.md, failedBps, bdType)
  3119  		}
  3120  	}
  3121  	return nil
  3122  }
  3123  
  3124  func (fbo *folderBlockOps) doDeferredWritesLocked(ctx context.Context,
  3125  	lState *kbfssync.LockState, kmd KeyMetadataWithRootDirEntry,
  3126  	oldPath, newPath data.Path) (stillDirty bool, err error) {
  3127  	fbo.blockLock.AssertLocked(lState)
  3128  
  3129  	// Redo any writes or truncates that happened to our file while
  3130  	// the sync was happening.
  3131  	ds := fbo.deferred[oldPath.TailRef()]
  3132  	stillDirty = len(ds.writes) != 0
  3133  	delete(fbo.deferred, oldPath.TailRef())
  3134  
  3135  	// Clear any dirty blocks that resulted from a write/truncate
  3136  	// happening during the sync, since we're redoing them below.
  3137  	dirtyBcache := fbo.config.DirtyBlockCache()
  3138  	for _, ptr := range ds.dirtyDeletes {
  3139  		fbo.vlog.CLogf(
  3140  			ctx, libkb.VLog1, "Deleting deferred dirty ptr %v", ptr)
  3141  		if err := dirtyBcache.Delete(fbo.id(), ptr, fbo.branch()); err != nil {
  3142  			return true, err
  3143  		}
  3144  	}
  3145  
  3146  	for _, f := range ds.writes {
  3147  		err = f(ctx, lState, kmd, newPath)
  3148  		if err != nil {
  3149  			// It's a little weird to return an error from a deferred
  3150  			// write here. Hopefully that will never happen.
  3151  			return true, err
  3152  		}
  3153  	}
  3154  	return stillDirty, nil
  3155  }
  3156  
  3157  // FinishSyncLocked finishes the sync process for a file, given the
  3158  // state from StartSync. Specifically, it re-applies any writes that
  3159  // happened since the call to StartSync.
  3160  func (fbo *folderBlockOps) FinishSyncLocked(
  3161  	ctx context.Context, lState *kbfssync.LockState,
  3162  	oldPath, newPath data.Path, md ReadOnlyRootMetadata,
  3163  	syncState fileSyncState, fbm *folderBlockManager) (
  3164  	stillDirty bool, err error) {
  3165  	fbo.blockLock.AssertLocked(lState)
  3166  
  3167  	dirtyBcache := fbo.config.DirtyBlockCache()
  3168  	for _, ptr := range syncState.oldFileBlockPtrs {
  3169  		fbo.vlog.CLogf(ctx, libkb.VLog1, "Deleting dirty ptr %v", ptr)
  3170  		if err := dirtyBcache.Delete(fbo.id(), ptr, fbo.branch()); err != nil {
  3171  			return true, err
  3172  		}
  3173  	}
  3174  
  3175  	bcache := fbo.config.BlockCache()
  3176  	for _, ptr := range syncState.newIndirectFileBlockPtrs {
  3177  		err := bcache.DeletePermanent(ptr.ID)
  3178  		if err != nil {
  3179  			fbo.log.CWarningf(ctx, "Error when deleting %v from cache: %v",
  3180  				ptr.ID, err)
  3181  		}
  3182  	}
  3183  
  3184  	stillDirty, err = fbo.doDeferredWritesLocked(
  3185  		ctx, lState, md, oldPath, newPath)
  3186  	if err != nil {
  3187  		return true, err
  3188  	}
  3189  
  3190  	// Clear cached info for the old path.  We are guaranteed that any
  3191  	// concurrent write to this file was deferred, even if it was to a
  3192  	// block that wasn't currently being sync'd, since the top-most
  3193  	// block is always in dirtyFiles and is always dirtied during a
  3194  	// write/truncate.
  3195  	//
  3196  	// Also, we can get rid of all the sync state that might have
  3197  	// happened during the sync, since we will replay the writes
  3198  	// below anyway.
  3199  	if err := fbo.clearCacheInfoLocked(lState, oldPath); err != nil {
  3200  		return true, err
  3201  	}
  3202  
  3203  	if err := fbo.cleanUpUnusedBlocks(ctx, md, syncState, fbm); err != nil {
  3204  		return true, err
  3205  	}
  3206  
  3207  	return stillDirty, nil
  3208  }
  3209  
  3210  // notifyErrListeners notifies any write operations that are blocked
  3211  // on a file so that they can learn about unrecoverable sync errors.
  3212  func (fbo *folderBlockOps) notifyErrListenersLocked(
  3213  	lState *kbfssync.LockState, ptr data.BlockPointer, err error) {
  3214  	fbo.blockLock.AssertLocked(lState)
  3215  	if isRecoverableBlockError(err) {
  3216  		// Don't bother any listeners with this error, since the sync
  3217  		// will be retried.  Unless the sync has reached its retry
  3218  		// limit, but in that case the listeners will just proceed as
  3219  		// normal once the dirty block cache bytes are freed, and
  3220  		// that's ok since this error isn't fatal.
  3221  		return
  3222  	}
  3223  	df := fbo.dirtyFiles[ptr]
  3224  	if df != nil {
  3225  		df.NotifyErrListeners(err)
  3226  	}
  3227  }
  3228  
  3229  type searchWithOutOfDateCacheError struct {
  3230  }
  3231  
  3232  func (e searchWithOutOfDateCacheError) Error() string {
  3233  	return fmt.Sprintf("Search is using an out-of-date node cache; " +
  3234  		"try again with a clean cache.")
  3235  }
  3236  
  3237  // searchForNodesInDirLocked recursively tries to find a path, and
  3238  // ultimately a node, to ptr, given the set of pointers that were
  3239  // updated in a particular operation.  The keys in nodeMap make up the
  3240  // set of BlockPointers that are being searched for, and nodeMap is
  3241  // updated in place to include the corresponding discovered nodes.
  3242  //
  3243  // Returns the number of nodes found by this invocation.  If the error
  3244  // it returns is searchWithOutOfDateCache, the search should be
  3245  // retried by the caller with a clean cache.
  3246  func (fbo *folderBlockOps) searchForNodesInDirLocked(ctx context.Context,
  3247  	lState *kbfssync.LockState, cache NodeCache, newPtrs map[data.BlockPointer]bool,
  3248  	kmd libkey.KeyMetadata, rootNode Node, currDir data.Path, nodeMap map[data.BlockPointer]Node,
  3249  	numNodesFoundSoFar int) (int, error) {
  3250  	fbo.blockLock.AssertAnyLocked(lState)
  3251  
  3252  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  3253  	if err != nil {
  3254  		return 0, err
  3255  	}
  3256  	dd := fbo.newDirDataLocked(lState, currDir, chargedTo, kmd)
  3257  	entries, err := dd.GetEntries(ctx)
  3258  	if err != nil {
  3259  		return 0, err
  3260  	}
  3261  
  3262  	// getDirLocked may have unlocked blockLock, which means the cache
  3263  	// could have changed out from under us.  Verify that didn't
  3264  	// happen, so we can avoid messing it up with nodes from an old MD
  3265  	// version.  If it did happen, return a special error that lets
  3266  	// the caller know they should retry with a fresh cache.
  3267  	if currDir.Path[0].BlockPointer !=
  3268  		cache.PathFromNode(rootNode).TailPointer() {
  3269  		return 0, searchWithOutOfDateCacheError{}
  3270  	}
  3271  
  3272  	if numNodesFoundSoFar >= len(nodeMap) {
  3273  		return 0, nil
  3274  	}
  3275  
  3276  	numNodesFound := 0
  3277  	for name, de := range entries {
  3278  		childPath := currDir.ChildPath(name, de.BlockPointer, nil)
  3279  		if _, ok := nodeMap[de.BlockPointer]; ok {
  3280  			// make a node for every pathnode
  3281  			n := rootNode
  3282  			for i, pn := range childPath.Path[1:] {
  3283  				if !pn.BlockPointer.IsValid() {
  3284  					// Temporary debugging output for KBFS-1764 -- the
  3285  					// GetOrCreate call below will panic.
  3286  					fbo.log.CDebugf(ctx, "Invalid block pointer, path=%s, "+
  3287  						"path.path=%v (index %d), name=%s, de=%#v, "+
  3288  						"nodeMap=%v, newPtrs=%v, kmd=%#v",
  3289  						childPath, childPath.Path, i, name, de, nodeMap,
  3290  						newPtrs, kmd)
  3291  				}
  3292  				et := data.Dir
  3293  				if i == len(childPath.Path)-2 {
  3294  					et = de.Type
  3295  				}
  3296  				n, err = cache.GetOrCreate(pn.BlockPointer, pn.Name, n, et)
  3297  				if err != nil {
  3298  					return 0, err
  3299  				}
  3300  			}
  3301  			childPath.ChildObfuscator = n.Obfuscator()
  3302  			nodeMap[de.BlockPointer] = n
  3303  			numNodesFound++
  3304  			if numNodesFoundSoFar+numNodesFound >= len(nodeMap) {
  3305  				return numNodesFound, nil
  3306  			}
  3307  		}
  3308  
  3309  		// otherwise, recurse if this represents an updated block
  3310  		if _, ok := newPtrs[de.BlockPointer]; de.Type == data.Dir && ok {
  3311  			if childPath.Obfuscator() == nil {
  3312  				childPath.ChildObfuscator = fbo.nodeCache.ObfuscatorMaker()()
  3313  			}
  3314  			n, err := fbo.searchForNodesInDirLocked(ctx, lState, cache,
  3315  				newPtrs, kmd, rootNode, childPath, nodeMap,
  3316  				numNodesFoundSoFar+numNodesFound)
  3317  			if err != nil {
  3318  				return 0, err
  3319  			}
  3320  			numNodesFound += n
  3321  			if numNodesFoundSoFar+numNodesFound >= len(nodeMap) {
  3322  				return numNodesFound, nil
  3323  			}
  3324  		}
  3325  	}
  3326  
  3327  	return numNodesFound, nil
  3328  }
  3329  
  3330  func (fbo *folderBlockOps) trySearchWithCacheLocked(ctx context.Context,
  3331  	lState *kbfssync.LockState, cache NodeCache, ptrs []data.BlockPointer,
  3332  	newPtrs map[data.BlockPointer]bool, kmd libkey.KeyMetadata, rootPtr data.BlockPointer) (
  3333  	map[data.BlockPointer]Node, error) {
  3334  	fbo.blockLock.AssertAnyLocked(lState)
  3335  
  3336  	nodeMap := make(map[data.BlockPointer]Node)
  3337  	for _, ptr := range ptrs {
  3338  		nodeMap[ptr] = nil
  3339  	}
  3340  
  3341  	if len(ptrs) == 0 {
  3342  		return nodeMap, nil
  3343  	}
  3344  
  3345  	var node Node
  3346  	// The node cache used by the main part of KBFS is
  3347  	// fbo.nodeCache. This basically maps from BlockPointers to
  3348  	// Nodes. Nodes are used by the callers of the library, but
  3349  	// internally we need to know the series of BlockPointers and
  3350  	// file/dir names that make up the path of the corresponding
  3351  	// file/dir. fbo.nodeCache is long-lived and never invalidated.
  3352  	//
  3353  	// As folderBranchOps gets informed of new local or remote MD
  3354  	// updates, which change the BlockPointers of some subset of the
  3355  	// nodes in this TLF, it calls nodeCache.UpdatePointer for each
  3356  	// change. Then, when a caller passes some old Node they have
  3357  	// lying around into an FBO call, we can translate it to its
  3358  	// current path using fbo.nodeCache. Note that on every TLF
  3359  	// modification, we are guaranteed that the BlockPointer of the
  3360  	// root directory will change (because of the merkle-ish tree of
  3361  	// content hashes we use to assign BlockPointers).
  3362  	//
  3363  	// fbo.nodeCache needs to maintain the absolute latest mappings
  3364  	// for the TLF, or else FBO calls won't see up-to-date data. The
  3365  	// tension in search comes from the fact that we are trying to
  3366  	// discover the BlockPointers of certain files at a specific point
  3367  	// in the MD history, which is not necessarily the same as the
  3368  	// most-recently-seen MD update. Specifically, some callers
  3369  	// process a specific range of MDs, but folderBranchOps may have
  3370  	// heard about a newer one before, or during, when the caller
  3371  	// started processing. That means fbo.nodeCache may have been
  3372  	// updated to reflect the newest BlockPointers, and is no longer
  3373  	// correct as a cache for our search for the data at the old point
  3374  	// in time.
  3375  	if cache == fbo.nodeCache {
  3376  		// Root node should already exist if we have an up-to-date md.
  3377  		node = cache.Get(rootPtr.Ref())
  3378  		if node == nil {
  3379  			return nil, searchWithOutOfDateCacheError{}
  3380  		}
  3381  	} else {
  3382  		// Root node may or may not exist.
  3383  		var err error
  3384  		node, err = cache.GetOrCreate(rootPtr,
  3385  			data.NewPathPartString(
  3386  				string(kmd.GetTlfHandle().GetCanonicalName()), nil),
  3387  			nil, data.Dir)
  3388  		if err != nil {
  3389  			return nil, err
  3390  		}
  3391  	}
  3392  	if node == nil {
  3393  		return nil, fmt.Errorf("Cannot find root node corresponding to %v",
  3394  			rootPtr)
  3395  	}
  3396  
  3397  	// are they looking for the root directory?
  3398  	numNodesFound := 0
  3399  	if _, ok := nodeMap[rootPtr]; ok {
  3400  		nodeMap[rootPtr] = node
  3401  		numNodesFound++
  3402  		if numNodesFound >= len(nodeMap) {
  3403  			return nodeMap, nil
  3404  		}
  3405  	}
  3406  
  3407  	rootPath := cache.PathFromNode(node)
  3408  	if len(rootPath.Path) != 1 {
  3409  		return nil, fmt.Errorf("Invalid root path for %v: %s",
  3410  			rootPtr, rootPath)
  3411  	}
  3412  
  3413  	_, err := fbo.searchForNodesInDirLocked(ctx, lState, cache, newPtrs,
  3414  		kmd, node, rootPath, nodeMap, numNodesFound)
  3415  	if err != nil {
  3416  		return nil, err
  3417  	}
  3418  
  3419  	if rootPtr != cache.PathFromNode(node).TailPointer() {
  3420  		return nil, searchWithOutOfDateCacheError{}
  3421  	}
  3422  
  3423  	return nodeMap, nil
  3424  }
  3425  
  3426  func (fbo *folderBlockOps) searchForNodesLocked(ctx context.Context,
  3427  	lState *kbfssync.LockState, cache NodeCache, ptrs []data.BlockPointer,
  3428  	newPtrs map[data.BlockPointer]bool, kmd libkey.KeyMetadata,
  3429  	rootPtr data.BlockPointer) (map[data.BlockPointer]Node, NodeCache, error) {
  3430  	fbo.blockLock.AssertAnyLocked(lState)
  3431  
  3432  	// First try the passed-in cache.  If it doesn't work because the
  3433  	// cache is out of date, try again with a clean cache.
  3434  	nodeMap, err := fbo.trySearchWithCacheLocked(ctx, lState, cache, ptrs,
  3435  		newPtrs, kmd, rootPtr)
  3436  	if _, ok := err.(searchWithOutOfDateCacheError); ok {
  3437  		// The md is out-of-date, so use a throwaway cache so we
  3438  		// don't pollute the real node cache with stale nodes.
  3439  		fbo.vlog.CLogf(
  3440  			ctx, libkb.VLog1, "Root node %v doesn't exist in the node "+
  3441  				"cache; using a throwaway node cache instead",
  3442  			rootPtr)
  3443  		cache = newNodeCacheStandard(fbo.folderBranch)
  3444  		cache.SetObfuscatorMaker(fbo.nodeCache.ObfuscatorMaker())
  3445  		nodeMap, err = fbo.trySearchWithCacheLocked(ctx, lState, cache, ptrs,
  3446  			newPtrs, kmd, rootPtr)
  3447  	}
  3448  
  3449  	if err != nil {
  3450  		return nil, nil, err
  3451  	}
  3452  
  3453  	// Return the whole map even if some nodes weren't found.
  3454  	return nodeMap, cache, nil
  3455  }
  3456  
  3457  // SearchForNodes tries to resolve all the given pointers to a Node
  3458  // object, using only the updated pointers specified in newPtrs.
  3459  // Returns an error if any subset of the pointer paths do not exist;
  3460  // it is the caller's responsibility to decide to error on particular
  3461  // unresolved nodes.  It also returns the cache that ultimately
  3462  // contains the nodes -- this might differ from the passed-in cache if
  3463  // another goroutine updated that cache and it no longer contains the
  3464  // root pointer specified in md.
  3465  func (fbo *folderBlockOps) SearchForNodes(ctx context.Context,
  3466  	cache NodeCache, ptrs []data.BlockPointer, newPtrs map[data.BlockPointer]bool,
  3467  	kmd libkey.KeyMetadata, rootPtr data.BlockPointer) (
  3468  	map[data.BlockPointer]Node, NodeCache, error) {
  3469  	lState := makeFBOLockState()
  3470  	fbo.blockLock.RLock(lState)
  3471  	defer fbo.blockLock.RUnlock(lState)
  3472  	return fbo.searchForNodesLocked(
  3473  		ctx, lState, cache, ptrs, newPtrs, kmd, rootPtr)
  3474  }
  3475  
  3476  // SearchForPaths is like SearchForNodes, except it returns a
  3477  // consistent view of all the paths of the searched-for pointers.
  3478  func (fbo *folderBlockOps) SearchForPaths(ctx context.Context,
  3479  	cache NodeCache, ptrs []data.BlockPointer, newPtrs map[data.BlockPointer]bool,
  3480  	kmd libkey.KeyMetadata, rootPtr data.BlockPointer) (map[data.BlockPointer]data.Path, error) {
  3481  	lState := makeFBOLockState()
  3482  	// Hold the lock while processing the paths so they can't be changed.
  3483  	fbo.blockLock.RLock(lState)
  3484  	defer fbo.blockLock.RUnlock(lState)
  3485  	nodeMap, cache, err :=
  3486  		fbo.searchForNodesLocked(
  3487  			ctx, lState, cache, ptrs, newPtrs, kmd, rootPtr)
  3488  	if err != nil {
  3489  		return nil, err
  3490  	}
  3491  
  3492  	paths := make(map[data.BlockPointer]data.Path)
  3493  	for ptr, n := range nodeMap {
  3494  		if n == nil {
  3495  			paths[ptr] = data.Path{}
  3496  			continue
  3497  		}
  3498  
  3499  		p := cache.PathFromNode(n)
  3500  		if p.TailPointer() != ptr {
  3501  			return nil, NodeNotFoundError{ptr}
  3502  		}
  3503  		paths[ptr] = p
  3504  	}
  3505  
  3506  	return paths, nil
  3507  }
  3508  
  3509  // UpdateCachedEntryAttributesOnRemovedFile updates any cached entry
  3510  // for the given path of an unlinked file, according to the given op,
  3511  // and it makes a new dirty cache entry if one doesn't exist yet.  We
  3512  // assume Sync will be called eventually on the corresponding open
  3513  // file handle, which will clear out the entry.
  3514  func (fbo *folderBlockOps) UpdateCachedEntryAttributesOnRemovedFile(
  3515  	ctx context.Context, lState *kbfssync.LockState,
  3516  	kmd KeyMetadataWithRootDirEntry, op *setAttrOp, p data.Path, de data.DirEntry) error {
  3517  	fbo.blockLock.Lock(lState)
  3518  	defer fbo.blockLock.Unlock(lState)
  3519  	_, err := fbo.setCachedAttrLocked(
  3520  		ctx, lState, kmd, *p.ParentPath(), p.TailName(), op.Attr, de)
  3521  	return err
  3522  }
  3523  
  3524  func (fbo *folderBlockOps) getDeferredWriteCountForTest(
  3525  	lState *kbfssync.LockState) int {
  3526  	fbo.blockLock.RLock(lState)
  3527  	defer fbo.blockLock.RUnlock(lState)
  3528  	writes := 0
  3529  	for _, ds := range fbo.deferred {
  3530  		writes += len(ds.writes)
  3531  	}
  3532  	return writes
  3533  }
  3534  
  3535  func (fbo *folderBlockOps) updatePointer(kmd libkey.KeyMetadata, oldPtr data.BlockPointer, newPtr data.BlockPointer, shouldPrefetch bool) NodeID {
  3536  	updatedNode := fbo.nodeCache.UpdatePointer(oldPtr.Ref(), newPtr)
  3537  	if updatedNode == nil || oldPtr.ID == newPtr.ID {
  3538  		return nil
  3539  	}
  3540  
  3541  	// Only prefetch if the updated pointer is a new block ID.
  3542  	// TODO: Remove this comment when we're done debugging because it'll be everywhere.
  3543  	ctx := context.TODO()
  3544  	fbo.vlog.CLogf(
  3545  		ctx, libkb.VLog1, "Updated reference for pointer %s to %s.",
  3546  		oldPtr.ID, newPtr.ID)
  3547  	if shouldPrefetch {
  3548  		// Prefetch the new ref, but only if the old ref already exists in
  3549  		// the block cache. Ideally we'd always prefetch it, but we need
  3550  		// the type of the block so that we can call `NewEmpty`.
  3551  		block, lifetime, err := fbo.config.BlockCache().GetWithLifetime(oldPtr)
  3552  		if err != nil {
  3553  			return updatedNode
  3554  		}
  3555  
  3556  		// No need to cache because it's already cached.
  3557  		action := fbo.config.Mode().DefaultBlockRequestAction()
  3558  		if fbo.branch() != data.MasterBranch {
  3559  			action = action.AddNonMasterBranch()
  3560  		}
  3561  		_ = fbo.config.BlockOps().BlockRetriever().Request(
  3562  			ctx, updatePointerPrefetchPriority, kmd, newPtr, block.NewEmpty(),
  3563  			lifetime, action)
  3564  	}
  3565  	// Cancel any prefetches for the old pointer from the prefetcher.
  3566  	fbo.config.BlockOps().Prefetcher().CancelPrefetch(oldPtr)
  3567  	return updatedNode
  3568  }
  3569  
  3570  // UpdatePointers updates all the pointers in the node cache
  3571  // atomically.  If `afterUpdateFn` is non-nil, it's called under the
  3572  // same block lock under which the pointers were updated.
  3573  func (fbo *folderBlockOps) UpdatePointers(
  3574  	kmd libkey.KeyMetadata, lState *kbfssync.LockState, op op, shouldPrefetch bool,
  3575  	afterUpdateFn func() error) (affectedNodeIDs []NodeID, err error) {
  3576  	fbo.blockLock.Lock(lState)
  3577  	defer fbo.blockLock.Unlock(lState)
  3578  	for _, update := range op.allUpdates() {
  3579  		updatedNode := fbo.updatePointer(
  3580  			kmd, update.Unref, update.Ref, shouldPrefetch)
  3581  		if updatedNode != nil {
  3582  			affectedNodeIDs = append(affectedNodeIDs, updatedNode)
  3583  		}
  3584  	}
  3585  
  3586  	// Cancel any prefetches for all unreferenced block pointers.
  3587  	for _, unref := range op.Unrefs() {
  3588  		fbo.config.BlockOps().Prefetcher().CancelPrefetch(unref)
  3589  	}
  3590  
  3591  	if afterUpdateFn == nil {
  3592  		return affectedNodeIDs, nil
  3593  	}
  3594  
  3595  	return affectedNodeIDs, afterUpdateFn()
  3596  }
  3597  
  3598  func (fbo *folderBlockOps) unlinkDuringFastForwardLocked(ctx context.Context,
  3599  	lState *kbfssync.LockState, kmd KeyMetadataWithRootDirEntry, ref data.BlockRef) (undoFn func()) {
  3600  	fbo.blockLock.AssertLocked(lState)
  3601  	oldNode := fbo.nodeCache.Get(ref)
  3602  	if oldNode == nil {
  3603  		return nil
  3604  	}
  3605  	oldPath := fbo.nodeCache.PathFromNode(oldNode)
  3606  	fbo.vlog.CLogf(
  3607  		ctx, libkb.VLog1, "Unlinking missing node %s/%v during "+
  3608  			"fast-forward", oldPath, ref)
  3609  	de, err := fbo.getEntryLocked(ctx, lState, kmd, oldPath, true)
  3610  	if err != nil {
  3611  		fbo.log.CDebugf(ctx, "Couldn't find old dir entry for %s/%v: %+v",
  3612  			oldPath, ref, err)
  3613  	}
  3614  	return fbo.nodeCache.Unlink(ref, oldPath, de)
  3615  }
  3616  
  3617  type nodeChildrenMap map[string]map[data.PathNode]bool
  3618  
  3619  func (ncm nodeChildrenMap) addDirChange(
  3620  	node Node, p data.Path, changes []NodeChange, affectedNodeIDs []NodeID) (
  3621  	[]NodeChange, []NodeID) {
  3622  	change := NodeChange{Node: node}
  3623  	for subchild := range ncm[p.String()] {
  3624  		change.DirUpdated = append(change.DirUpdated, subchild.Name)
  3625  	}
  3626  	changes = append(changes, change)
  3627  	affectedNodeIDs = append(affectedNodeIDs, node.GetID())
  3628  	return changes, affectedNodeIDs
  3629  }
  3630  
  3631  func (nodeChildrenMap) addFileChange(
  3632  	node Node, changes []NodeChange, affectedNodeIDs []NodeID) (
  3633  	[]NodeChange, []NodeID) {
  3634  	// Invalidate the entire file contents.
  3635  	changes = append(changes, NodeChange{
  3636  		Node:        node,
  3637  		FileUpdated: []WriteRange{{Len: 0, Off: 0}},
  3638  	})
  3639  	affectedNodeIDs = append(affectedNodeIDs, node.GetID())
  3640  	return changes, affectedNodeIDs
  3641  }
  3642  
  3643  func (fbo *folderBlockOps) fastForwardDirAndChildrenLocked(ctx context.Context,
  3644  	lState *kbfssync.LockState, currDir data.Path, children nodeChildrenMap,
  3645  	kmd KeyMetadataWithRootDirEntry,
  3646  	updates map[data.BlockPointer]data.BlockPointer) (
  3647  	changes []NodeChange, affectedNodeIDs []NodeID, undoFns []func(),
  3648  	err error) {
  3649  	fbo.blockLock.AssertLocked(lState)
  3650  
  3651  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  3652  	if err != nil {
  3653  		return nil, nil, undoFns, err
  3654  	}
  3655  	dd := fbo.newDirDataLocked(lState, currDir, chargedTo, kmd)
  3656  	entries, err := dd.GetEntries(ctx)
  3657  	if err != nil {
  3658  		return nil, nil, undoFns, err
  3659  	}
  3660  
  3661  	prefix := currDir.String()
  3662  
  3663  	// TODO: parallelize me?
  3664  	for child := range children[prefix] {
  3665  		entry, ok := entries[child.Name]
  3666  		if !ok {
  3667  			undoFn := fbo.unlinkDuringFastForwardLocked(
  3668  				ctx, lState, kmd, child.BlockPointer.Ref())
  3669  			if undoFn != nil {
  3670  				undoFns = append(undoFns, undoFn)
  3671  			}
  3672  			continue
  3673  		}
  3674  
  3675  		fbo.vlog.CLogf(
  3676  			ctx, libkb.VLog1, "Fast-forwarding %v -> %v",
  3677  			child.BlockPointer, entry.BlockPointer)
  3678  		fbo.updatePointer(kmd, child.BlockPointer,
  3679  			entry.BlockPointer, true)
  3680  		updates[child.BlockPointer] = entry.BlockPointer
  3681  		node := fbo.nodeCache.Get(entry.BlockPointer.Ref())
  3682  		if node == nil {
  3683  			fbo.vlog.CLogf(
  3684  				ctx, libkb.VLog1, "Skipping missing node for %s",
  3685  				entry.BlockPointer)
  3686  			continue
  3687  		}
  3688  		if entry.Type == data.Dir {
  3689  			newPath := fbo.nodeCache.PathFromNode(node)
  3690  			changes, affectedNodeIDs = children.addDirChange(
  3691  				node, newPath, changes, affectedNodeIDs)
  3692  
  3693  			childChanges, childAffectedNodeIDs, childUndoFns, err :=
  3694  				fbo.fastForwardDirAndChildrenLocked(
  3695  					ctx, lState, newPath, children, kmd, updates)
  3696  			undoFns = append(undoFns, childUndoFns...)
  3697  			if err != nil {
  3698  				return nil, nil, undoFns, err
  3699  			}
  3700  			changes = append(changes, childChanges...)
  3701  			affectedNodeIDs = append(affectedNodeIDs, childAffectedNodeIDs...)
  3702  		} else {
  3703  			// File -- invalidate the entire file contents.
  3704  			changes, affectedNodeIDs = children.addFileChange(
  3705  				node, changes, affectedNodeIDs)
  3706  		}
  3707  	}
  3708  	delete(children, prefix)
  3709  	return changes, affectedNodeIDs, undoFns, nil
  3710  }
  3711  
  3712  func (fbo *folderBlockOps) makeChildrenTreeFromNodesLocked(
  3713  	lState *kbfssync.LockState, nodes []Node) (
  3714  	rootPath data.Path, children nodeChildrenMap) {
  3715  	fbo.blockLock.AssertLocked(lState)
  3716  
  3717  	// Build a "tree" representation for each interesting path prefix.
  3718  	children = make(nodeChildrenMap)
  3719  	for _, n := range nodes {
  3720  		p := fbo.nodeCache.PathFromNode(n)
  3721  		if len(p.Path) == 1 {
  3722  			rootPath = p
  3723  		}
  3724  		prevPath := ""
  3725  		for _, pn := range p.Path {
  3726  			if prevPath != "" {
  3727  				childPNs := children[prevPath]
  3728  				if childPNs == nil {
  3729  					childPNs = make(map[data.PathNode]bool)
  3730  					children[prevPath] = childPNs
  3731  				}
  3732  				childPNs[pn] = true
  3733  			}
  3734  			prevPath = pathlib.Join(prevPath, pn.Name.Plaintext())
  3735  		}
  3736  	}
  3737  	return rootPath, children
  3738  }
  3739  
  3740  // FastForwardAllNodes attempts to update the block pointers
  3741  // associated with nodes in the cache by searching for their paths in
  3742  // the current version of the TLF.  If it can't find a corresponding
  3743  // node, it assumes it's been deleted and unlinks it.  Returns the set
  3744  // of node changes that resulted.  If there are no nodes, it returns a
  3745  // nil error because there's nothing to be done.
  3746  func (fbo *folderBlockOps) FastForwardAllNodes(ctx context.Context,
  3747  	lState *kbfssync.LockState, md ReadOnlyRootMetadata) (
  3748  	changes []NodeChange, affectedNodeIDs []NodeID, err error) {
  3749  	if fbo.nodeCache == nil {
  3750  		// Nothing needs to be done!
  3751  		return nil, nil, nil
  3752  	}
  3753  
  3754  	// Take a hard lock through this whole process.  TODO: is there
  3755  	// any way to relax this?  It could lead to file system operation
  3756  	// timeouts, even on reads, if we hold it too long.
  3757  	fbo.blockLock.Lock(lState)
  3758  	defer fbo.blockLock.Unlock(lState)
  3759  
  3760  	nodes := fbo.nodeCache.AllNodes()
  3761  	if len(nodes) == 0 {
  3762  		// Nothing needs to be done!
  3763  		return nil, nil, nil
  3764  	}
  3765  	fbo.vlog.CLogf(ctx, libkb.VLog1, "Fast-forwarding %d nodes", len(nodes))
  3766  	defer func() {
  3767  		fbo.vlog.CLogf(ctx, libkb.VLog1, "Fast-forward complete: %v", err)
  3768  	}()
  3769  
  3770  	rootPath, children := fbo.makeChildrenTreeFromNodesLocked(lState, nodes)
  3771  	if !rootPath.IsValid() {
  3772  		return nil, nil, errors.New("Couldn't find the root path")
  3773  	}
  3774  
  3775  	fbo.vlog.CLogf(
  3776  		ctx, libkb.VLog1, "Fast-forwarding root %v -> %v",
  3777  		rootPath.Path[0].BlockPointer, md.data.Dir.BlockPointer)
  3778  	fbo.updatePointer(md, rootPath.Path[0].BlockPointer,
  3779  		md.data.Dir.BlockPointer, false)
  3780  
  3781  	// Keep track of all the pointer updates done, and unwind them if
  3782  	// there's any error.
  3783  	updates := make(map[data.BlockPointer]data.BlockPointer)
  3784  	updates[rootPath.Path[0].BlockPointer] = md.data.Dir.BlockPointer
  3785  	var undoFns []func()
  3786  	defer func() {
  3787  		if err == nil {
  3788  			return
  3789  		}
  3790  		for oldID, newID := range updates {
  3791  			fbo.updatePointer(md, newID, oldID, false)
  3792  		}
  3793  		for _, f := range undoFns {
  3794  			f()
  3795  		}
  3796  	}()
  3797  
  3798  	rootPath.Path[0].BlockPointer = md.data.Dir.BlockPointer
  3799  	rootNode := fbo.nodeCache.Get(md.data.Dir.BlockPointer.Ref())
  3800  	if rootNode != nil {
  3801  		change := NodeChange{Node: rootNode}
  3802  		for child := range children[rootPath.String()] {
  3803  			change.DirUpdated = append(change.DirUpdated, child.Name)
  3804  		}
  3805  		changes = append(changes, change)
  3806  		affectedNodeIDs = append(affectedNodeIDs, rootNode.GetID())
  3807  	}
  3808  
  3809  	childChanges, childAffectedNodeIDs, undoFns, err :=
  3810  		fbo.fastForwardDirAndChildrenLocked(
  3811  			ctx, lState, rootPath, children, md, updates)
  3812  	if err != nil {
  3813  		return nil, nil, err
  3814  	}
  3815  	changes = append(changes, childChanges...)
  3816  	affectedNodeIDs = append(affectedNodeIDs, childAffectedNodeIDs...)
  3817  
  3818  	// Unlink any children that remain.
  3819  	for _, childPNs := range children {
  3820  		for child := range childPNs {
  3821  			fbo.unlinkDuringFastForwardLocked(
  3822  				ctx, lState, md, child.BlockPointer.Ref())
  3823  		}
  3824  	}
  3825  	return changes, affectedNodeIDs, nil
  3826  }
  3827  
  3828  func (fbo *folderBlockOps) getInvalidationChangesForNodes(
  3829  	ctx context.Context, lState *kbfssync.LockState, nodes []Node) (
  3830  	changes []NodeChange, affectedNodeIDs []NodeID, err error) {
  3831  	fbo.blockLock.AssertLocked(lState)
  3832  	if len(nodes) == 0 {
  3833  		// Nothing needs to be done!
  3834  		return nil, nil, nil
  3835  	}
  3836  
  3837  	_, children := fbo.makeChildrenTreeFromNodesLocked(lState, nodes)
  3838  	for _, node := range nodes {
  3839  		p := fbo.nodeCache.PathFromNode(node)
  3840  		prefix := p.String()
  3841  		childNodes := children[prefix]
  3842  		if len(childNodes) > 0 {
  3843  			// This must be a directory.  Invalidate all children.
  3844  			changes, affectedNodeIDs = children.addDirChange(
  3845  				node, p, changes, affectedNodeIDs)
  3846  			fbo.vlog.CLogf(
  3847  				ctx, libkb.VLog1, "Invalidating dir node %p/%s", node, prefix)
  3848  		} else {
  3849  			// This might be a file.  In any case, it doesn't have any
  3850  			// children that need invalidation, so just send the file
  3851  			// change.
  3852  			changes, affectedNodeIDs = children.addFileChange(
  3853  				node, changes, affectedNodeIDs)
  3854  			fbo.vlog.CLogf(
  3855  				ctx, libkb.VLog1, "Invalidating possible file node %p/%s",
  3856  				node, prefix)
  3857  		}
  3858  	}
  3859  	return changes, affectedNodeIDs, nil
  3860  }
  3861  
  3862  // GetInvalidationChangesForNode returns the list of invalidation
  3863  // notifications for all the nodes rooted at the given node.
  3864  func (fbo *folderBlockOps) GetInvalidationChangesForNode(
  3865  	ctx context.Context, lState *kbfssync.LockState, node Node) (
  3866  	changes []NodeChange, affectedNodeIDs []NodeID, err error) {
  3867  	if fbo.nodeCache == nil {
  3868  		// Nothing needs to be done!
  3869  		return nil, nil, nil
  3870  	}
  3871  
  3872  	fbo.blockLock.Lock(lState)
  3873  	defer fbo.blockLock.Unlock(lState)
  3874  	fbo.vlog.CLogf(
  3875  		ctx, libkb.VLog1, "About to get all children for node %p", node)
  3876  	childNodes := fbo.nodeCache.AllNodeChildren(node)
  3877  	fbo.vlog.CLogf(
  3878  		ctx, libkb.VLog1, "Found %d children for node %p", len(childNodes),
  3879  		node)
  3880  	return fbo.getInvalidationChangesForNodes(
  3881  		ctx, lState, append(childNodes, node))
  3882  }
  3883  
  3884  // GetInvalidationChangesForAll returns the list of invalidation
  3885  // notifications for the entire TLF.
  3886  func (fbo *folderBlockOps) GetInvalidationChangesForAll(
  3887  	ctx context.Context, lState *kbfssync.LockState) (
  3888  	changes []NodeChange, affectedNodeIDs []NodeID, err error) {
  3889  	if fbo.nodeCache == nil {
  3890  		// Nothing needs to be done!
  3891  		return nil, nil, nil
  3892  	}
  3893  
  3894  	fbo.blockLock.Lock(lState)
  3895  	defer fbo.blockLock.Unlock(lState)
  3896  	childNodes := fbo.nodeCache.AllNodes()
  3897  	fbo.vlog.CLogf(ctx, libkb.VLog1, "Found %d nodes", len(childNodes))
  3898  	return fbo.getInvalidationChangesForNodes(ctx, lState, childNodes)
  3899  }
  3900  
  3901  // MarkNode marks all the blocks in the node's block tree with the
  3902  // given tag.
  3903  func (fbo *folderBlockOps) MarkNode(
  3904  	ctx context.Context, lState *kbfssync.LockState, node Node, kmd libkey.KeyMetadata,
  3905  	tag string, cacheType DiskBlockCacheType) error {
  3906  	dbc := fbo.config.DiskBlockCache()
  3907  	if dbc == nil {
  3908  		return nil
  3909  	}
  3910  
  3911  	fbo.blockLock.RLock(lState)
  3912  	defer fbo.blockLock.RUnlock(lState)
  3913  
  3914  	chargedTo, err := fbo.getChargedToLocked(ctx, lState, kmd)
  3915  	if err != nil {
  3916  		return err
  3917  	}
  3918  	p := fbo.nodeCache.PathFromNode(node)
  3919  	err = dbc.Mark(ctx, p.TailPointer().ID, tag, cacheType)
  3920  	if err != nil {
  3921  		return err
  3922  	}
  3923  	var infos []data.BlockInfo
  3924  	if node.EntryType() == data.Dir {
  3925  		dd := fbo.newDirDataLocked(lState, p, chargedTo, kmd)
  3926  		infos, err = dd.GetIndirectDirBlockInfos(ctx)
  3927  	} else {
  3928  		fd := fbo.newFileData(lState, p, chargedTo, kmd)
  3929  		infos, err = fd.GetIndirectFileBlockInfos(ctx)
  3930  	}
  3931  	if err != nil {
  3932  		return err
  3933  	}
  3934  
  3935  	for _, info := range infos {
  3936  		err = dbc.Mark(ctx, info.BlockPointer.ID, tag, cacheType)
  3937  		switch errors.Cause(err).(type) {
  3938  		case nil:
  3939  		case data.NoSuchBlockError:
  3940  		default:
  3941  			return err
  3942  		}
  3943  	}
  3944  	return nil
  3945  }
  3946  
  3947  type chainsPathPopulator interface {
  3948  	populateChainPaths(context.Context, logger.Logger, *crChains, bool) error
  3949  	obfuscatorMaker() func() data.Obfuscator
  3950  }
  3951  
  3952  // populateChainPaths updates all the paths in all the ops tracked by
  3953  // `chains`, using the main nodeCache.
  3954  func (fbo *folderBlockOps) populateChainPaths(ctx context.Context,
  3955  	log logger.Logger, chains *crChains, includeCreates bool) error {
  3956  	_, err := chains.getPaths(
  3957  		ctx, fbo, log, fbo.nodeCache, includeCreates,
  3958  		fbo.config.Mode().IsTestMode())
  3959  	return err
  3960  }
  3961  
  3962  func (fbo *folderBlockOps) obfuscatorMaker() func() data.Obfuscator {
  3963  	return fbo.nodeCache.ObfuscatorMaker()
  3964  }
  3965  
  3966  var _ chainsPathPopulator = (*folderBlockOps)(nil)