1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package modcmd 6 7import ( 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "io/fs" 13 "os" 14 "runtime" 15 16 "cmd/go/internal/base" 17 "cmd/go/internal/gover" 18 "cmd/go/internal/modfetch" 19 "cmd/go/internal/modload" 20 21 "golang.org/x/mod/module" 22 "golang.org/x/mod/sumdb/dirhash" 23) 24 25var cmdVerify = &base.Command{ 26 UsageLine: "go mod verify", 27 Short: "verify dependencies have expected content", 28 Long: ` 29Verify checks that the dependencies of the current module, 30which are stored in a local downloaded source cache, have not been 31modified since being downloaded. If all the modules are unmodified, 32verify prints "all modules verified." Otherwise it reports which 33modules have been changed and causes 'go mod' to exit with a 34non-zero status. 35 36See https://golang.org/ref/mod#go-mod-verify for more about 'go mod verify'. 37 `, 38 Run: runVerify, 39} 40 41func init() { 42 base.AddChdirFlag(&cmdVerify.Flag) 43 base.AddModCommonFlags(&cmdVerify.Flag) 44} 45 46func runVerify(ctx context.Context, cmd *base.Command, args []string) { 47 modload.InitWorkfile() 48 49 if len(args) != 0 { 50 // NOTE(rsc): Could take a module pattern. 51 base.Fatalf("go: verify takes no arguments") 52 } 53 modload.ForceUseModules = true 54 modload.RootMode = modload.NeedRoot 55 56 // Only verify up to GOMAXPROCS zips at once. 57 type token struct{} 58 sem := make(chan token, runtime.GOMAXPROCS(0)) 59 60 mg, err := modload.LoadModGraph(ctx, "") 61 if err != nil { 62 base.Fatal(err) 63 } 64 mods := mg.BuildList() 65 // Use a slice of result channels, so that the output is deterministic. 66 errsChans := make([]<-chan []error, len(mods)) 67 68 for i, mod := range mods { 69 sem <- token{} 70 errsc := make(chan []error, 1) 71 errsChans[i] = errsc 72 mod := mod // use a copy to avoid data races 73 go func() { 74 errsc <- verifyMod(ctx, mod) 75 <-sem 76 }() 77 } 78 79 ok := true 80 for _, errsc := range errsChans { 81 errs := <-errsc 82 for _, err := range errs { 83 base.Errorf("%s", err) 84 ok = false 85 } 86 } 87 if ok { 88 fmt.Printf("all modules verified\n") 89 } 90} 91 92func verifyMod(ctx context.Context, mod module.Version) []error { 93 if gover.IsToolchain(mod.Path) { 94 // "go" and "toolchain" have no disk footprint; nothing to verify. 95 return nil 96 } 97 if modload.MainModules.Contains(mod.Path) { 98 return nil 99 } 100 var errs []error 101 zip, zipErr := modfetch.CachePath(ctx, mod, "zip") 102 if zipErr == nil { 103 _, zipErr = os.Stat(zip) 104 } 105 dir, dirErr := modfetch.DownloadDir(ctx, mod) 106 data, err := os.ReadFile(zip + "hash") 107 if err != nil { 108 if zipErr != nil && errors.Is(zipErr, fs.ErrNotExist) && 109 dirErr != nil && errors.Is(dirErr, fs.ErrNotExist) { 110 // Nothing downloaded yet. Nothing to verify. 111 return nil 112 } 113 errs = append(errs, fmt.Errorf("%s %s: missing ziphash: %v", mod.Path, mod.Version, err)) 114 return errs 115 } 116 h := string(bytes.TrimSpace(data)) 117 118 if zipErr != nil && errors.Is(zipErr, fs.ErrNotExist) { 119 // ok 120 } else { 121 hZ, err := dirhash.HashZip(zip, dirhash.DefaultHash) 122 if err != nil { 123 errs = append(errs, fmt.Errorf("%s %s: %v", mod.Path, mod.Version, err)) 124 return errs 125 } else if hZ != h { 126 errs = append(errs, fmt.Errorf("%s %s: zip has been modified (%v)", mod.Path, mod.Version, zip)) 127 } 128 } 129 if dirErr != nil && errors.Is(dirErr, fs.ErrNotExist) { 130 // ok 131 } else { 132 hD, err := dirhash.HashDir(dir, mod.Path+"@"+mod.Version, dirhash.DefaultHash) 133 if err != nil { 134 135 errs = append(errs, fmt.Errorf("%s %s: %v", mod.Path, mod.Version, err)) 136 return errs 137 } 138 if hD != h { 139 errs = append(errs, fmt.Errorf("%s %s: dir has been modified (%v)", mod.Path, mod.Version, dir)) 140 } 141 } 142 return errs 143} 144