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 }