github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/state_checker.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  	"errors"
     9  	"fmt"
    10  	"reflect"
    11  	"time"
    12  
    13  	"github.com/keybase/client/go/kbfs/data"
    14  	"github.com/keybase/client/go/kbfs/kbfsblock"
    15  	"github.com/keybase/client/go/kbfs/kbfsmd"
    16  	"github.com/keybase/client/go/kbfs/kbfssync"
    17  	"github.com/keybase/client/go/kbfs/libkey"
    18  	"github.com/keybase/client/go/kbfs/tlf"
    19  	"github.com/keybase/client/go/logger"
    20  	"github.com/keybase/client/go/protocol/keybase1"
    21  	"golang.org/x/net/context"
    22  )
    23  
    24  // StateChecker verifies that the server-side state for KBFS is
    25  // consistent.  Useful mostly for testing because it isn't scalable
    26  // and loads all the state in memory.
    27  type StateChecker struct {
    28  	config Config
    29  	log    logger.Logger
    30  }
    31  
    32  // NewStateChecker returns a new StateChecker instance.
    33  func NewStateChecker(config Config) *StateChecker {
    34  	return &StateChecker{config, config.MakeLogger("")}
    35  }
    36  
    37  // findAllFileBlocks adds all file blocks found under this block to
    38  // the blockSizes map, if the given path represents an indirect block.
    39  func (sc *StateChecker) findAllFileBlocks(ctx context.Context,
    40  	lState *kbfssync.LockState, ops *folderBranchOps, kmd libkey.KeyMetadata,
    41  	file data.Path, blockSizes map[data.BlockPointer]uint32) error {
    42  	infos, err := ops.blocks.GetIndirectFileBlockInfos(ctx, lState, kmd, file)
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	for _, info := range infos {
    48  		blockSizes[info.BlockPointer] = info.EncodedSize
    49  	}
    50  	return nil
    51  }
    52  
    53  // findAllDirBlocks adds all dir blocks found under this block to the
    54  // blockSizes map, if the given path represents an indirect block.
    55  func (sc *StateChecker) findAllDirBlocks(ctx context.Context,
    56  	lState *kbfssync.LockState, ops *folderBranchOps, kmd libkey.KeyMetadata,
    57  	dir data.Path, blockSizes map[data.BlockPointer]uint32) error {
    58  	infos, err := ops.blocks.GetIndirectDirBlockInfos(ctx, lState, kmd, dir)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	for _, info := range infos {
    64  		blockSizes[info.BlockPointer] = info.EncodedSize
    65  	}
    66  	return nil
    67  }
    68  
    69  // findAllBlocksInPath adds all blocks found within this directory to
    70  // the blockSizes map, and then recursively checks all
    71  // subdirectories.
    72  func (sc *StateChecker) findAllBlocksInPath(ctx context.Context,
    73  	lState *kbfssync.LockState, ops *folderBranchOps, kmd libkey.KeyMetadata,
    74  	dir data.Path, blockSizes map[data.BlockPointer]uint32) error {
    75  	children, err := ops.blocks.GetEntries(ctx, lState, kmd, dir)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	err = sc.findAllDirBlocks(ctx, lState, ops, kmd, dir, blockSizes)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	for name, de := range children {
    86  		if de.Type == data.Sym {
    87  			continue
    88  		}
    89  
    90  		blockSizes[de.BlockPointer] = de.EncodedSize
    91  		p := dir.ChildPath(name, de.BlockPointer, ops.makeObfuscator())
    92  
    93  		if de.Type == data.Dir {
    94  			err := sc.findAllBlocksInPath(ctx, lState, ops, kmd, p, blockSizes)
    95  			if err != nil {
    96  				return err
    97  			}
    98  		} else {
    99  			// If it's a file, check to see if it's indirect.
   100  			err := sc.findAllFileBlocks(ctx, lState, ops, kmd, p, blockSizes)
   101  			if err != nil {
   102  				return err
   103  			}
   104  		}
   105  	}
   106  	return nil
   107  }
   108  
   109  func (sc *StateChecker) getLastGCData(ctx context.Context,
   110  	tlfID tlf.ID) (time.Time, kbfsmd.Revision) {
   111  	config, ok := sc.config.(*ConfigLocal)
   112  	if !ok {
   113  		return time.Time{}, kbfsmd.RevisionUninitialized
   114  	}
   115  
   116  	var latestTime time.Time
   117  	var latestRev kbfsmd.Revision
   118  	for _, c := range *config.allKnownConfigsForTesting {
   119  		ops := c.KBFSOps().(*KBFSOpsStandard).getOpsIfExists(
   120  			context.Background(),
   121  			data.FolderBranch{Tlf: tlfID, Branch: data.MasterBranch})
   122  		if ops == nil {
   123  			continue
   124  		}
   125  		rt, rev := ops.fbm.getLastQRData()
   126  		if rt.After(latestTime) && rev > latestRev {
   127  			latestTime = rt
   128  			latestRev = rev
   129  		}
   130  	}
   131  	if latestTime.IsZero() {
   132  		return latestTime, latestRev
   133  	}
   134  
   135  	sc.log.CDebugf(ctx, "Last qr data for TLF %s: revTime=%s, rev=%d",
   136  		tlfID, latestTime, latestRev)
   137  	return latestTime.Add(
   138  		-sc.config.Mode().QuotaReclamationMinUnrefAge()), latestRev
   139  }
   140  
   141  // CheckMergedState verifies that the state for the given tlf is
   142  // consistent.
   143  func (sc *StateChecker) CheckMergedState(ctx context.Context, tlfID tlf.ID) error {
   144  	// Blow away MD cache so we don't have any lingering re-embedded
   145  	// block changes (otherwise we won't be able to learn their sizes).
   146  	sc.config.SetMDCache(NewMDCacheStandard(defaultMDCacheCapacity))
   147  
   148  	// Fetch all the MD updates for this folder, and use the block
   149  	// change lists to build up the set of currently referenced blocks.
   150  	rmds, err := getMergedMDUpdates(ctx, sc.config, tlfID,
   151  		kbfsmd.RevisionInitial, nil)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	if len(rmds) == 0 {
   156  		sc.log.CDebugf(ctx, "No state to check for folder %s", tlfID)
   157  		return nil
   158  	}
   159  
   160  	lState := makeFBOLockState()
   161  
   162  	// Re-embed block changes.
   163  	kbfsOps, ok := sc.config.KBFSOps().(*KBFSOpsStandard)
   164  	if !ok {
   165  		return errors.New("Unexpected KBFSOps type")
   166  	}
   167  
   168  	fb := data.FolderBranch{Tlf: tlfID, Branch: data.MasterBranch}
   169  	ops := kbfsOps.getOps(context.Background(), fb, FavoritesOpNoChange)
   170  	lastGCRevisionTime, lastGCRev := sc.getLastGCData(ctx, tlfID)
   171  
   172  	// Build the expected block list.
   173  	expectedLiveBlocks := make(map[data.BlockPointer]bool)
   174  	expectedRef := uint64(0)
   175  	expectedMDRef := uint64(0)
   176  	archivedBlocks := make(map[data.BlockPointer]bool)
   177  	actualLiveBlocks := make(map[data.BlockPointer]uint32)
   178  
   179  	// See what the last GC op revision is.  All unref'd pointers from
   180  	// that revision or earlier should be deleted from the block
   181  	// server.
   182  	gcRevision := kbfsmd.RevisionUninitialized
   183  	for _, rmd := range rmds {
   184  		// Don't process copies.
   185  		if rmd.IsWriterMetadataCopiedSet() {
   186  			continue
   187  		}
   188  
   189  		for _, op := range rmd.data.Changes.Ops {
   190  			GCOp, ok := op.(*GCOp)
   191  			if !ok {
   192  				continue
   193  			}
   194  			gcRevision = GCOp.LatestRev
   195  		}
   196  	}
   197  
   198  	for _, rmd := range rmds {
   199  		// Don't process copies.
   200  		if rmd.IsWriterMetadataCopiedSet() {
   201  			continue
   202  		}
   203  		// Unembedded block changes count towards the MD size.
   204  		if info := rmd.data.cachedChanges.Info; info.BlockPointer != data.ZeroPtr {
   205  			sc.log.CDebugf(ctx, "Unembedded block change: %v, %d",
   206  				info.BlockPointer, info.EncodedSize)
   207  			actualLiveBlocks[info.BlockPointer] = info.EncodedSize
   208  
   209  			// Any child block change pointers?
   210  			file := data.Path{
   211  				FolderBranch: data.FolderBranch{
   212  					Tlf: tlfID, Branch: data.MasterBranch},
   213  				Path: []data.PathNode{{
   214  					BlockPointer: info.BlockPointer,
   215  					Name: data.NewPathPartString(fmt.Sprintf(
   216  						"<MD with revision %d>", rmd.Revision()), nil),
   217  				}}}
   218  			err := sc.findAllFileBlocks(ctx, lState, ops, rmd.ReadOnly(),
   219  				file, actualLiveBlocks)
   220  			if err != nil {
   221  				return err
   222  			}
   223  		}
   224  
   225  		var hasGCOp bool
   226  		updated := make(map[data.BlockPointer]bool)
   227  		for _, op := range rmd.data.Changes.Ops {
   228  			_, isGCOp := op.(*GCOp)
   229  			hasGCOp = hasGCOp || isGCOp
   230  
   231  			opRefs := make(map[data.BlockPointer]bool)
   232  			for _, ptr := range op.Refs() {
   233  				if ptr != data.ZeroPtr {
   234  					expectedLiveBlocks[ptr] = true
   235  					opRefs[ptr] = true
   236  				}
   237  			}
   238  			if !isGCOp {
   239  				for _, ptr := range op.Unrefs() {
   240  					if updated[ptr] {
   241  						return fmt.Errorf(
   242  							"%s already updated in this revision %d",
   243  							ptr, rmd.Revision())
   244  					}
   245  					delete(expectedLiveBlocks, ptr)
   246  					if ptr != data.ZeroPtr {
   247  						// If the revision has been garbage-collected,
   248  						// or if the pointer has been referenced and
   249  						// unreferenced within the same op (which
   250  						// indicates a failed and retried sync), the
   251  						// corresponding block should already be
   252  						// cleaned up.
   253  						if rmd.Revision() <= gcRevision || opRefs[ptr] {
   254  							delete(archivedBlocks, ptr)
   255  						} else {
   256  							archivedBlocks[ptr] = true
   257  						}
   258  					}
   259  				}
   260  			}
   261  			for _, update := range op.allUpdates() {
   262  				if update.Ref != update.Unref {
   263  					updated[update.Unref] = true
   264  					delete(expectedLiveBlocks, update.Unref)
   265  				}
   266  				if update.Unref != data.ZeroPtr && update.Ref != update.Unref {
   267  					if rmd.Revision() <= gcRevision {
   268  						delete(archivedBlocks, update.Unref)
   269  					} else {
   270  						archivedBlocks[update.Unref] = true
   271  					}
   272  				}
   273  				if update.Ref != data.ZeroPtr && update.Ref != update.Unref {
   274  					expectedLiveBlocks[update.Ref] = true
   275  				}
   276  			}
   277  		}
   278  		expectedRef += rmd.RefBytes()
   279  		expectedRef -= rmd.UnrefBytes()
   280  		expectedMDRef += rmd.MDRefBytes()
   281  
   282  		if len(rmd.data.Changes.Ops) == 1 && hasGCOp {
   283  			// Don't check GC status for GC revisions
   284  			continue
   285  		}
   286  
   287  		// Make sure that if this revision should be covered by a GC
   288  		// op, it is.  Note that this assumes that if QR is ever run,
   289  		// it will be run completely and not left partially done due
   290  		// to there being too many pointers to collect in one sweep.
   291  		mtime := time.Unix(0, rmd.data.Dir.Mtime)
   292  		if !lastGCRevisionTime.Before(mtime) && rmd.Revision() <= lastGCRev &&
   293  			rmd.Revision() > gcRevision {
   294  			return fmt.Errorf("Revision %d happened on or before the last "+
   295  				"gc time %s rev %d, but was not included in the latest "+
   296  				"gc op revision %d", rmd.Revision(), lastGCRevisionTime,
   297  				lastGCRev, gcRevision)
   298  		}
   299  	}
   300  	sc.log.CDebugf(ctx, "Folder %v has %d expected live blocks, "+
   301  		"total %d bytes (%d MD bytes)", tlfID, len(expectedLiveBlocks),
   302  		expectedRef, expectedMDRef)
   303  
   304  	currMD := rmds[len(rmds)-1]
   305  	expectedUsage := currMD.DiskUsage()
   306  	if expectedUsage != expectedRef {
   307  		return fmt.Errorf("Expected ref bytes %d doesn't match latest disk "+
   308  			"usage %d", expectedRef, expectedUsage)
   309  	}
   310  	expectedMDUsage := currMD.MDDiskUsage()
   311  	if expectedMDUsage != expectedMDRef {
   312  		return fmt.Errorf("Expected MD ref bytes %d doesn't match latest disk "+
   313  			"MD usage %d", expectedMDRef, expectedMDUsage)
   314  	}
   315  
   316  	// Then, using the current MD head, start at the root of the FS
   317  	// and recursively walk the directory tree to find all the blocks
   318  	// that are currently accessible.
   319  	rootNode, _, _, err := ops.getRootNode(ctx)
   320  	if err != nil {
   321  		return err
   322  	}
   323  	rootPath := ops.nodeCache.PathFromNode(rootNode)
   324  	if g, e := rootPath.TailPointer(), currMD.data.Dir.BlockPointer; g != e {
   325  		return fmt.Errorf("Current MD root pointer %v doesn't match root "+
   326  			"node pointer %v", e, g)
   327  	}
   328  	actualLiveBlocks[rootPath.TailPointer()] = currMD.data.Dir.EncodedSize
   329  	if err := sc.findAllBlocksInPath(ctx, lState, ops, currMD.ReadOnly(),
   330  		rootPath, actualLiveBlocks); err != nil {
   331  		return err
   332  	}
   333  	sc.log.CDebugf(ctx, "Folder %v has %d actual live blocks",
   334  		tlfID, len(actualLiveBlocks))
   335  
   336  	// Compare the two and see if there are any differences. Don't use
   337  	// reflect.DeepEqual so we can print out exactly what's wrong.
   338  	var extraBlocks []data.BlockPointer
   339  	actualSize := uint64(0)
   340  	actualMDSize := uint64(0)
   341  	for ptr, size := range actualLiveBlocks {
   342  		if ptr.GetBlockType() == keybase1.BlockType_MD {
   343  			actualMDSize += uint64(size)
   344  		} else {
   345  			actualSize += uint64(size)
   346  		}
   347  		if !expectedLiveBlocks[ptr] {
   348  			extraBlocks = append(extraBlocks, ptr)
   349  		}
   350  	}
   351  	if len(extraBlocks) != 0 {
   352  		sc.log.CWarningf(ctx, "%v: Extra live blocks found: %v",
   353  			tlfID, extraBlocks)
   354  		return fmt.Errorf("Folder %v has inconsistent state", tlfID)
   355  	}
   356  	var missingBlocks []data.BlockPointer
   357  	for ptr := range expectedLiveBlocks {
   358  		if _, ok := actualLiveBlocks[ptr]; !ok {
   359  			missingBlocks = append(missingBlocks, ptr)
   360  		}
   361  	}
   362  	if len(missingBlocks) != 0 {
   363  		sc.log.CWarningf(ctx, "%v: Expected live blocks not found: %v",
   364  			tlfID, missingBlocks)
   365  		return fmt.Errorf("Folder %v has inconsistent state", tlfID)
   366  	}
   367  
   368  	if actualSize != expectedRef {
   369  		return fmt.Errorf("Actual size %d doesn't match expected size %d",
   370  			actualSize, expectedRef)
   371  	}
   372  	if actualMDSize != expectedMDRef {
   373  		return fmt.Errorf("Actual MD size %d doesn't match expected MD size %d",
   374  			actualMDSize, expectedMDRef)
   375  	}
   376  
   377  	// Check that the set of referenced blocks matches exactly what
   378  	// the block server knows about.
   379  	bserverLocal, ok := sc.config.BlockServer().(blockServerLocal)
   380  	if !ok {
   381  		if jbs, jok := sc.config.BlockServer().(journalBlockServer); jok {
   382  			bserverLocal, ok = jbs.BlockServer.(blockServerLocal)
   383  			if !ok {
   384  				sc.log.CDebugf(ctx, "Bad block server: %T", jbs.BlockServer)
   385  			}
   386  		}
   387  	}
   388  	if !ok {
   389  		return errors.New("StateChecker only works against BlockServerLocal")
   390  	}
   391  	bserverKnownBlocks, err := bserverLocal.getAllRefsForTest(ctx, tlfID)
   392  	if err != nil {
   393  		return err
   394  	}
   395  
   396  	blockRefsByID := make(map[kbfsblock.ID]blockRefMap)
   397  	for ptr := range expectedLiveBlocks {
   398  		if _, ok := blockRefsByID[ptr.ID]; !ok {
   399  			blockRefsByID[ptr.ID] = make(blockRefMap)
   400  		}
   401  		err := blockRefsByID[ptr.ID].put(ptr.Context, liveBlockRef, "")
   402  		if err != nil {
   403  			return err
   404  		}
   405  	}
   406  	for ptr := range archivedBlocks {
   407  		if _, ok := blockRefsByID[ptr.ID]; !ok {
   408  			blockRefsByID[ptr.ID] = make(blockRefMap)
   409  		}
   410  		err := blockRefsByID[ptr.ID].put(ptr.Context, archivedBlockRef, "")
   411  		if err != nil {
   412  			return err
   413  		}
   414  	}
   415  
   416  	if g, e := bserverKnownBlocks, blockRefsByID; !reflect.DeepEqual(g, e) {
   417  		for id, eRefs := range e {
   418  			if gRefs := g[id]; !reflect.DeepEqual(gRefs, eRefs) {
   419  				sc.log.CDebugf(ctx, "Refs for ID %v don't match.  "+
   420  					"Got %v, expected %v", id, gRefs, eRefs)
   421  			}
   422  		}
   423  		for id, gRefs := range g {
   424  			if _, ok := e[id]; !ok {
   425  				sc.log.CDebugf(ctx, "Did not find matching expected "+
   426  					"ID for found block %v (with refs %v)", id, gRefs)
   427  			}
   428  		}
   429  
   430  		return fmt.Errorf("Folder %v has inconsistent state", tlfID)
   431  	}
   432  
   433  	// TODO: Check the archived and deleted blocks as well.
   434  	return nil
   435  }