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

View as plain text