Source file src/cmd/go/internal/modcmd/verify.go

     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  
     5  package modcmd
     6  
     7  import (
     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  
    25  var cmdVerify = &base.Command{
    26  	UsageLine: "go mod verify",
    27  	Short:     "verify dependencies have expected content",
    28  	Long: `
    29  Verify checks that the dependencies of the current module,
    30  which are stored in a local downloaded source cache, have not been
    31  modified since being downloaded. If all the modules are unmodified,
    32  verify prints "all modules verified." Otherwise it reports which
    33  modules have been changed and causes 'go mod' to exit with a
    34  non-zero status.
    35  
    36  See https://golang.org/ref/mod#go-mod-verify for more about 'go mod verify'.
    37  	`,
    38  	Run: runVerify,
    39  }
    40  
    41  func init() {
    42  	base.AddChdirFlag(&cmdVerify.Flag)
    43  	base.AddModCommonFlags(&cmdVerify.Flag)
    44  }
    45  
    46  func 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  
    92  func 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  

View as plain text