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  	moduleLoaderState := modload.NewState()
    48  	moduleLoaderState.InitWorkfile()
    49  
    50  	if len(args) != 0 {
    51  		// NOTE(rsc): Could take a module pattern.
    52  		base.Fatalf("go: verify takes no arguments")
    53  	}
    54  	moduleLoaderState.ForceUseModules = true
    55  	moduleLoaderState.RootMode = modload.NeedRoot
    56  
    57  	// Only verify up to GOMAXPROCS zips at once.
    58  	type token struct{}
    59  	sem := make(chan token, runtime.GOMAXPROCS(0))
    60  
    61  	mg, err := modload.LoadModGraph(moduleLoaderState, ctx, "")
    62  	if err != nil {
    63  		base.Fatal(err)
    64  	}
    65  	mods := mg.BuildList()
    66  	// Use a slice of result channels, so that the output is deterministic.
    67  	errsChans := make([]<-chan []error, len(mods))
    68  
    69  	for i, mod := range mods {
    70  		sem <- token{}
    71  		errsc := make(chan []error, 1)
    72  		errsChans[i] = errsc
    73  		mod := mod // use a copy to avoid data races
    74  		go func() {
    75  			errsc <- verifyMod(moduleLoaderState, ctx, mod)
    76  			<-sem
    77  		}()
    78  	}
    79  
    80  	ok := true
    81  	for _, errsc := range errsChans {
    82  		errs := <-errsc
    83  		for _, err := range errs {
    84  			base.Errorf("%s", err)
    85  			ok = false
    86  		}
    87  	}
    88  	if ok {
    89  		fmt.Printf("all modules verified\n")
    90  	}
    91  }
    92  
    93  func verifyMod(loaderstate *modload.State, ctx context.Context, mod module.Version) []error {
    94  	if gover.IsToolchain(mod.Path) {
    95  		// "go" and "toolchain" have no disk footprint; nothing to verify.
    96  		return nil
    97  	}
    98  	if loaderstate.MainModules.Contains(mod.Path) {
    99  		return nil
   100  	}
   101  	var errs []error
   102  	zip, zipErr := modfetch.CachePath(ctx, mod, "zip")
   103  	if zipErr == nil {
   104  		_, zipErr = os.Stat(zip)
   105  	}
   106  	dir, dirErr := modfetch.DownloadDir(ctx, mod)
   107  	data, err := os.ReadFile(zip + "hash")
   108  	if err != nil {
   109  		if zipErr != nil && errors.Is(zipErr, fs.ErrNotExist) &&
   110  			dirErr != nil && errors.Is(dirErr, fs.ErrNotExist) {
   111  			// Nothing downloaded yet. Nothing to verify.
   112  			return nil
   113  		}
   114  		errs = append(errs, fmt.Errorf("%s %s: missing ziphash: %v", mod.Path, mod.Version, err))
   115  		return errs
   116  	}
   117  	h := string(bytes.TrimSpace(data))
   118  
   119  	if zipErr != nil && errors.Is(zipErr, fs.ErrNotExist) {
   120  		// ok
   121  	} else {
   122  		hZ, err := dirhash.HashZip(zip, dirhash.DefaultHash)
   123  		if err != nil {
   124  			errs = append(errs, fmt.Errorf("%s %s: %v", mod.Path, mod.Version, err))
   125  			return errs
   126  		} else if hZ != h {
   127  			errs = append(errs, fmt.Errorf("%s %s: zip has been modified (%v)", mod.Path, mod.Version, zip))
   128  		}
   129  	}
   130  	if dirErr != nil && errors.Is(dirErr, fs.ErrNotExist) {
   131  		// ok
   132  	} else {
   133  		hD, err := dirhash.HashDir(dir, mod.Path+"@"+mod.Version, dirhash.DefaultHash)
   134  		if err != nil {
   135  
   136  			errs = append(errs, fmt.Errorf("%s %s: %v", mod.Path, mod.Version, err))
   137  			return errs
   138  		}
   139  		if hD != h {
   140  			errs = append(errs, fmt.Errorf("%s %s: dir has been modified (%v)", mod.Path, mod.Version, dir))
   141  		}
   142  	}
   143  	return errs
   144  }
   145  

View as plain text