github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/kbfstool/md_check.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"path/filepath"
     7  
     8  	"github.com/keybase/client/go/kbfs/data"
     9  	"github.com/keybase/client/go/kbfs/libkbfs"
    10  	"github.com/keybase/client/go/kbfs/libkey"
    11  	"golang.org/x/net/context"
    12  )
    13  
    14  const mdCheckUsageStr = `Usage:
    15    kbfstool md check input [inputs...]
    16  
    17  Each input must be in the same format as in md dump. However,
    18  revisions in a revision range rev1-rev2 are always checked in
    19  descending order, regardless of whether rev1 <= rev2 or rev1 > rev2.
    20  
    21  `
    22  
    23  // TODO: The below checks could be sped up by fetching blocks in
    24  // parallel.
    25  
    26  // TODO: Factor out common code with StateChecker.findAllBlocksInPath.
    27  
    28  func checkDirBlock(ctx context.Context, config libkbfs.Config,
    29  	name string, kmd libkey.KeyMetadata, info data.BlockInfo,
    30  	verbose bool) (err error) {
    31  	if verbose {
    32  		fmt.Printf("Checking %s (dir block %v)...\n", name, info)
    33  	} else {
    34  		fmt.Printf("Checking %s...\n", name)
    35  	}
    36  	defer func() {
    37  		if err != nil {
    38  			fmt.Printf("Got error while checking %s: %v\n",
    39  				name, err)
    40  		}
    41  	}()
    42  
    43  	var dirBlock data.DirBlock
    44  	err = config.BlockOps().Get(
    45  		ctx, kmd, info.BlockPointer, &dirBlock, data.NoCacheEntry,
    46  		data.MasterBranch)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	for entryName, entry := range dirBlock.Children {
    52  		switch entry.Type {
    53  		case data.File, data.Exec:
    54  			_ = checkFileBlock(
    55  				ctx, config, filepath.Join(name, entryName),
    56  				kmd, entry.BlockInfo, verbose)
    57  		case data.Dir:
    58  			_ = checkDirBlock(
    59  				ctx, config, filepath.Join(name, entryName),
    60  				kmd, entry.BlockInfo, verbose)
    61  		case data.Sym:
    62  			if verbose {
    63  				fmt.Printf("Skipping symlink %s -> %s\n",
    64  					entryName, entry.SymPath)
    65  			}
    66  			continue
    67  		default:
    68  			fmt.Printf("Entry %s has unknown type %s",
    69  				entryName, entry.Type)
    70  		}
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  func checkFileBlock(ctx context.Context, config libkbfs.Config,
    77  	name string, kmd libkey.KeyMetadata, info data.BlockInfo,
    78  	verbose bool) (err error) {
    79  	if verbose {
    80  		fmt.Printf("Checking %s (file block %v)...\n", name, info)
    81  	} else {
    82  		fmt.Printf("Checking %s...\n", name)
    83  	}
    84  	defer func() {
    85  		if err != nil {
    86  			fmt.Printf("Got error while checking %s: %v\n",
    87  				name, err)
    88  		}
    89  	}()
    90  
    91  	var fileBlock data.FileBlock
    92  	err = config.BlockOps().Get(
    93  		ctx, kmd, info.BlockPointer, &fileBlock, data.NoCacheEntry,
    94  		data.MasterBranch)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	if fileBlock.IsInd {
   100  		// TODO: Check continuity of off+len if Holes is false
   101  		// for all blocks.
   102  		for _, iptr := range fileBlock.IPtrs {
   103  			_ = checkFileBlock(
   104  				ctx, config,
   105  				fmt.Sprintf("%s (off=%d)", name, iptr.Off),
   106  				kmd, iptr.BlockInfo, verbose)
   107  		}
   108  	}
   109  	return nil
   110  }
   111  
   112  // mdCheckChain checks that every MD object in the given list is a
   113  // valid successor of the next object in the list. Along the way, it
   114  // also checks that the root blocks that haven't been
   115  // garbage-collected are present. It returns a list of MD objects with
   116  // valid roots, in reverse revision order. If multiple MD objects have
   117  // the same root (which are assumed to all be adjacent), the most
   118  // recent one is returned.
   119  func mdCheckChain(ctx context.Context, config libkbfs.Config,
   120  	reversedIRMDs []libkbfs.ImmutableRootMetadata, verbose bool) (
   121  	reversedIRMDsWithRoots []libkbfs.ImmutableRootMetadata) {
   122  	fmt.Printf("Checking chain from rev %d to %d...\n",
   123  		reversedIRMDs[0].Revision(), reversedIRMDs[len(reversedIRMDs)-1].Revision())
   124  	gcUnrefs := make(map[data.BlockRef]bool)
   125  	for i, irmd := range reversedIRMDs {
   126  		currRev := irmd.Revision()
   127  		mdData := irmd.Data()
   128  		rootPtr := mdData.Dir.BlockPointer
   129  		switch {
   130  		case !rootPtr.Ref().IsValid():
   131  			// This happens in the wild, but only for
   132  			// folders used for journal-related testing
   133  			// early on.
   134  			fmt.Printf("Skipping checking root for rev %d (is invalid)\n",
   135  				currRev)
   136  		case gcUnrefs[rootPtr.Ref()]:
   137  			if verbose {
   138  				fmt.Printf("Skipping checking root for rev %d (GCed)\n",
   139  					currRev)
   140  			}
   141  		default:
   142  			fmt.Printf("Checking root for rev %d (%s)...\n",
   143  				currRev, rootPtr.Ref())
   144  			var dirBlock data.DirBlock
   145  			err := config.BlockOps().Get(
   146  				ctx, irmd, rootPtr, &dirBlock, data.NoCacheEntry,
   147  				data.MasterBranch)
   148  			if err != nil {
   149  				fmt.Printf("Got error while checking root "+
   150  					"for rev %d: %v\n",
   151  					currRev, err)
   152  			} else if len(reversedIRMDsWithRoots) == 0 ||
   153  				reversedIRMDsWithRoots[len(reversedIRMDsWithRoots)-1].Data().Dir.BlockPointer != rootPtr {
   154  				reversedIRMDsWithRoots = append(reversedIRMDsWithRoots, irmd)
   155  			}
   156  		}
   157  
   158  		for _, op := range mdData.Changes.Ops {
   159  			if gcOp, ok := op.(*libkbfs.GCOp); ok {
   160  				for _, unref := range gcOp.Unrefs() {
   161  					gcUnrefs[unref.Ref()] = true
   162  				}
   163  			}
   164  		}
   165  
   166  		if i == len(reversedIRMDs)-1 {
   167  			break
   168  		}
   169  
   170  		irmdPrev := reversedIRMDs[i+1]
   171  		predRev := irmdPrev.Revision()
   172  
   173  		if verbose {
   174  			fmt.Printf("Checking %d -> %d link...\n",
   175  				predRev, currRev)
   176  		}
   177  		err := irmdPrev.CheckValidSuccessor(
   178  			irmdPrev.MdID(), irmd.ReadOnly())
   179  		if err != nil {
   180  			fmt.Printf("Got error while checking %d -> %d link: %v\n",
   181  				predRev, currRev, err)
   182  		}
   183  
   184  		irmd = irmdPrev
   185  	}
   186  	return reversedIRMDsWithRoots
   187  }
   188  
   189  func mdCheckIRMDs(ctx context.Context, config libkbfs.Config,
   190  	tlfStr, branchStr string, reversedIRMDs []libkbfs.ImmutableRootMetadata,
   191  	verbose bool) error {
   192  	reversedIRMDsWithRoots :=
   193  		mdCheckChain(ctx, config, reversedIRMDs, verbose)
   194  
   195  	fmt.Printf("Retrieved %d MD objects with roots\n", len(reversedIRMDsWithRoots))
   196  
   197  	for _, irmd := range reversedIRMDsWithRoots {
   198  		// No need to check the blocks for unembedded changes,
   199  		// since they're already checked upon retrieval.
   200  		name := mdJoinInput(tlfStr, branchStr, irmd.Revision().String(), "")
   201  		_ = checkDirBlock(ctx, config, name, irmd,
   202  			irmd.Data().Dir.BlockInfo, verbose)
   203  	}
   204  	return nil
   205  }
   206  
   207  func mdCheck(ctx context.Context, config libkbfs.Config, args []string) (
   208  	exitStatus int) {
   209  	flags := flag.NewFlagSet("kbfs md check", flag.ContinueOnError)
   210  	verbose := flags.Bool("v", false, "Print verbose output.")
   211  	err := flags.Parse(args)
   212  	if err != nil {
   213  		printError("md check", err)
   214  		return 1
   215  	}
   216  
   217  	inputs := flags.Args()
   218  	if len(inputs) < 1 {
   219  		fmt.Print(mdCheckUsageStr)
   220  		return 1
   221  	}
   222  
   223  	for _, input := range inputs {
   224  		tlfStr, branchStr, startStr, stopStr, err := mdSplitInput(input)
   225  		if err != nil {
   226  			printError("md check", err)
   227  			return 1
   228  		}
   229  
   230  		tlfID, branchID, start, stop, err :=
   231  			mdParseInput(ctx, config, tlfStr, branchStr, startStr, stopStr)
   232  		if err != nil {
   233  			printError("md check", err)
   234  			return 1
   235  		}
   236  
   237  		min := start
   238  		max := stop
   239  		if start > stop {
   240  			min = stop
   241  			max = start
   242  		}
   243  
   244  		// The returned RMDs are already verified, so we don't
   245  		// have to do anything else.
   246  		//
   247  		// TODO: Chunk the range between start and stop.
   248  		irmds, err := mdGet(ctx, config, tlfID, branchID, min, max)
   249  		if err != nil {
   250  			printError("md check", err)
   251  			return 1
   252  		}
   253  
   254  		if len(irmds) == 0 {
   255  			fmt.Printf("No result found for %q\n\n", input)
   256  			continue
   257  		}
   258  
   259  		reversedIRMDs := reverseIRMDList(irmds)
   260  		err = mdCheckIRMDs(ctx, config, tlfStr, branchStr, reversedIRMDs, *verbose)
   261  		if err != nil {
   262  			printError("md check", err)
   263  			return 1
   264  		}
   265  
   266  		fmt.Print("\n")
   267  	}
   268  
   269  	return 0
   270  }