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