Source file src/cmd/go/internal/modfetch/fetch.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 modfetch
     6  
     7  import (
     8  	"archive/zip"
     9  	"bytes"
    10  	"cmd/go/internal/base"
    11  	"cmd/go/internal/cfg"
    12  	"cmd/go/internal/fsys"
    13  	"cmd/go/internal/gover"
    14  	"cmd/go/internal/lockedfile"
    15  	"cmd/go/internal/str"
    16  	"cmd/go/internal/trace"
    17  	"cmd/internal/par"
    18  	"cmd/internal/robustio"
    19  	"context"
    20  	"crypto/sha256"
    21  	"encoding/base64"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"io/fs"
    26  	"os"
    27  	"path/filepath"
    28  	"sort"
    29  	"strings"
    30  	"sync"
    31  
    32  	"golang.org/x/mod/module"
    33  	"golang.org/x/mod/sumdb/dirhash"
    34  	modzip "golang.org/x/mod/zip"
    35  )
    36  
    37  var (
    38  	ErrToolchain = errors.New("internal error: invalid operation on toolchain module")
    39  	ErrFIPS140   = errors.New("golang.org/fips140 is bundled with the Go distribution and cannot be downloaded")
    40  )
    41  
    42  // Download downloads the specific module version to the
    43  // local download cache and returns the name of the directory
    44  // corresponding to the root of the module's file tree.
    45  func (f *Fetcher) Download(ctx context.Context, mod module.Version) (dir string, err error) {
    46  	if gover.IsToolchain(mod.Path) {
    47  		return "", ErrToolchain
    48  	}
    49  	if mod.Path == "golang.org/fips140" {
    50  		return "", ErrFIPS140
    51  	}
    52  	if err := checkCacheDir(ctx); err != nil {
    53  		base.Fatal(err)
    54  	}
    55  
    56  	// The par.Cache here avoids duplicate work.
    57  	return f.downloadCache.Do(mod, func() (string, error) {
    58  		dir, err := f.download(ctx, mod)
    59  		if err != nil {
    60  			return "", err
    61  		}
    62  		f.checkMod(ctx, mod)
    63  
    64  		// If go.mod exists (not an old legacy module), check version is not too new.
    65  		if data, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil {
    66  			goVersion := gover.GoModLookup(data, "go")
    67  			if gover.Compare(goVersion, gover.Local()) > 0 {
    68  				return "", &gover.TooNewError{What: mod.String(), GoVersion: goVersion}
    69  			}
    70  		} else if !errors.Is(err, fs.ErrNotExist) {
    71  			return "", err
    72  		}
    73  
    74  		return dir, nil
    75  	})
    76  }
    77  
    78  // Unzip is like Download but for GOFIPS140 zip files which ship with
    79  // the Go distribution itself.
    80  //
    81  // Unzip performs a check like the go.sum check for downloaded modules:
    82  // A cached copy of mod is used only if the module cache records ziphash
    83  // as its module zip hash. As with go.sum, this rejects a cached copy
    84  // that was populated from some other source; it does not detect
    85  // modifications made to the module cache directory itself.
    86  //
    87  // Otherwise, any existing copy is discarded, zipfile is unpacked
    88  // again, and ziphash is stamped upon success.
    89  //
    90  // If verify is non-nil, it is called before zipfile is unpacked.
    91  // Unzip does not call verify when it returns a cached copy.
    92  //
    93  // If verify returns an error, the module cache is left untouched and
    94  // Unzip returns that error.
    95  //
    96  // ziphash must be non-empty.
    97  func (f *Fetcher) Unzip(ctx context.Context, mod module.Version, zipfile, ziphash string, verify func() error) (dir string, err error) {
    98  	if ziphash == "" {
    99  		return "", module.VersionError(mod, errors.New("internal error: Unzip called with empty module zip hash"))
   100  	}
   101  	if err := checkCacheDir(ctx); err != nil {
   102  		base.Fatal(err)
   103  	}
   104  
   105  	return f.downloadCache.Do(mod, func() (string, error) {
   106  		ctx, span := trace.StartSpan(ctx, "modfetch.Unzip "+mod.String())
   107  		defer span.Done()
   108  
   109  		dir, err = DownloadDir(ctx, mod)
   110  		if err == nil {
   111  			// The directory has already been completely extracted (no .partial file exists).
   112  			ok, err := haveZipHash(ctx, mod, ziphash)
   113  			if err != nil {
   114  				return "", err
   115  			}
   116  			if ok {
   117  				return dir, nil
   118  			}
   119  		} else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
   120  			return "", err
   121  		}
   122  
   123  		return unzip(ctx, mod, zipfile, ziphash, verify)
   124  	})
   125  }
   126  
   127  func haveZipHash(ctx context.Context, mod module.Version, ziphash string) (bool, error) {
   128  	path, err := CachePath(ctx, mod, "ziphash")
   129  	if err != nil {
   130  		return false, err
   131  	}
   132  	data, err := lockedfile.Read(path)
   133  	if errors.Is(err, fs.ErrNotExist) {
   134  		return false, nil
   135  	}
   136  	if err != nil {
   137  		return false, err
   138  	}
   139  	return strings.TrimSpace(string(data)) == ziphash, nil
   140  }
   141  
   142  func writeZipHash(ctx context.Context, mod module.Version, ziphash string) error {
   143  	path, err := CachePath(ctx, mod, "ziphash")
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil {
   148  		return err
   149  	}
   150  	return lockedfile.Write(path, strings.NewReader(ziphash), 0o666)
   151  }
   152  
   153  func (f *Fetcher) download(ctx context.Context, mod module.Version) (dir string, err error) {
   154  	ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String())
   155  	defer span.Done()
   156  
   157  	dir, err = DownloadDir(ctx, mod)
   158  	if err == nil {
   159  		// The directory has already been completely extracted (no .partial file exists).
   160  		return dir, nil
   161  	} else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
   162  		return "", err
   163  	}
   164  
   165  	// To avoid cluttering the cache with extraneous files,
   166  	// DownloadZip uses the same lockfile as Download.
   167  	// Invoke DownloadZip before locking the file.
   168  	zipfile, err := f.DownloadZip(ctx, mod)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  
   173  	return unzip(ctx, mod, zipfile, "", nil)
   174  }
   175  
   176  // unzip extracts zipfile into the module cache directory for mod,
   177  // unless a complete copy is already there (and, if ziphash is non-empty,
   178  // the module cache records ziphash for it).
   179  //
   180  // If verify is non-nil, it is called once the decision to extract has
   181  // been made, before any existing copy is discarded.
   182  func unzip(ctx context.Context, mod module.Version, zipfile, ziphash string, verify func() error) (dir string, err error) {
   183  	unlock, err := lockVersion(ctx, mod)
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  	defer unlock()
   188  
   189  	ctx, span := trace.StartSpan(ctx, "unzip "+zipfile)
   190  	defer span.Done()
   191  
   192  	// Check whether the directory was populated while we were waiting on the lock.
   193  	dir, dirErr := DownloadDir(ctx, mod)
   194  	if dirErr == nil {
   195  		if ziphash == "" {
   196  			return dir, nil
   197  		}
   198  		ok, err := haveZipHash(ctx, mod, ziphash)
   199  		if err != nil {
   200  			return "", err
   201  		}
   202  		if ok {
   203  			return dir, nil
   204  		}
   205  		dirErr = &DownloadDirPartialError{dir, errors.New("ziphash file does not match")}
   206  	}
   207  	_, dirExists := dirErr.(*DownloadDirPartialError)
   208  
   209  	// We are going to extract zipfile. Verify it first, while the module
   210  	// cache is still untouched, so that a bad zip file never costs us a
   211  	// cached copy and a good one is verified at most once per extraction.
   212  	if verify != nil {
   213  		if err := verify(); err != nil {
   214  			return "", err
   215  		}
   216  	}
   217  
   218  	// Clean up any remaining temporary directories created by old versions
   219  	// (before 1.16), as well as partially extracted directories (indicated by
   220  	// DownloadDirPartialError, usually because of a .partial file). This is only
   221  	// safe to do because the lock file ensures that their writers are no longer
   222  	// active.
   223  	parentDir := filepath.Dir(dir)
   224  	tmpPrefix := filepath.Base(dir) + ".tmp-"
   225  	if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(parentDir), str.QuoteGlob(tmpPrefix)+"*")); err == nil {
   226  		for _, path := range old {
   227  			RemoveAll(path) // best effort
   228  		}
   229  	}
   230  	if dirExists {
   231  		if err := RemoveAll(dir); err != nil {
   232  			return "", err
   233  		}
   234  	}
   235  	// Remove any recorded module zip hash before extracting, and record the
   236  	// new one only after the .partial file is removed (below). That way a
   237  	// .ziphash file for the module exists only beside a completely extracted
   238  	// directory, no matter where an unpack is interrupted.
   239  	if ziphash != "" {
   240  		hashPath, err := CachePath(ctx, mod, "ziphash")
   241  		if err != nil {
   242  			return "", err
   243  		}
   244  		if err := robustio.RemoveAll(hashPath); err != nil {
   245  			return "", err
   246  		}
   247  	}
   248  
   249  	partialPath, err := CachePath(ctx, mod, "partial")
   250  	if err != nil {
   251  		return "", err
   252  	}
   253  
   254  	// Extract the module zip directory at its final location.
   255  	//
   256  	// To prevent other processes from reading the directory if we crash,
   257  	// create a .partial file before extracting the directory, and delete
   258  	// the .partial file afterward (all while holding the lock).
   259  	//
   260  	// Before Go 1.16, we extracted to a temporary directory with a random name
   261  	// then renamed it into place with os.Rename. On Windows, this failed with
   262  	// ERROR_ACCESS_DENIED when another process (usually an anti-virus scanner)
   263  	// opened files in the temporary directory.
   264  	//
   265  	// Go 1.14.2 and higher respect .partial files. Older versions may use
   266  	// partially extracted directories. 'go mod verify' can detect this,
   267  	// and 'go clean -modcache' can fix it.
   268  	if err := os.MkdirAll(parentDir, 0o777); err != nil {
   269  		return "", err
   270  	}
   271  	if err := os.WriteFile(partialPath, nil, 0o666); err != nil {
   272  		return "", err
   273  	}
   274  	if err := modzip.Unzip(dir, mod, zipfile); err != nil {
   275  		fmt.Fprintf(os.Stderr, "-> %s\n", err)
   276  		if rmErr := RemoveAll(dir); rmErr == nil {
   277  			os.Remove(partialPath)
   278  		}
   279  		return "", err
   280  	}
   281  	if err := os.Remove(partialPath); err != nil {
   282  		return "", err
   283  	}
   284  	// The directory is complete: record the module zip hash of the zip
   285  	// file it was extracted from. See the removal above.
   286  	if ziphash != "" {
   287  		if err := writeZipHash(ctx, mod, ziphash); err != nil {
   288  			return "", err
   289  		}
   290  	}
   291  
   292  	if !cfg.ModCacheRW {
   293  		makeDirsReadOnly(dir)
   294  	}
   295  	return dir, nil
   296  }
   297  
   298  var downloadZipCache par.ErrCache[module.Version, string]
   299  
   300  // DownloadZip downloads the specific module version to the
   301  // local zip cache and returns the name of the zip file.
   302  func (f *Fetcher) DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) {
   303  	if mod.Path == "golang.org/fips140" {
   304  		return "", ErrFIPS140
   305  	}
   306  
   307  	// The par.Cache here avoids duplicate work.
   308  	return downloadZipCache.Do(mod, func() (string, error) {
   309  		zipfile, err := CachePath(ctx, mod, "zip")
   310  		if err != nil {
   311  			return "", err
   312  		}
   313  		ziphashfile := zipfile + "hash"
   314  
   315  		// Return early if the zip and ziphash files exist.
   316  		if _, err := os.Stat(zipfile); err == nil {
   317  			if _, err := os.Stat(ziphashfile); err == nil {
   318  				if !HaveSum(f, mod) {
   319  					f.checkMod(ctx, mod)
   320  				}
   321  				return zipfile, nil
   322  			}
   323  		}
   324  
   325  		// The zip or ziphash file does not exist. Acquire the lock and create them.
   326  		if cfg.CmdName != "mod download" {
   327  			vers := mod.Version
   328  			if mod.Path == "golang.org/toolchain" {
   329  				// Shorten v0.0.1-go1.13.1.darwin-amd64 to go1.13.1.darwin-amd64
   330  				_, vers, _ = strings.Cut(vers, "-")
   331  				if i := strings.LastIndex(vers, "."); i >= 0 {
   332  					goos, goarch, _ := strings.Cut(vers[i+1:], "-")
   333  					vers = vers[:i] + " (" + goos + "/" + goarch + ")"
   334  				}
   335  				fmt.Fprintf(os.Stderr, "go: downloading %s\n", vers)
   336  			} else {
   337  				fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, vers)
   338  			}
   339  		}
   340  		unlock, err := lockVersion(ctx, mod)
   341  		if err != nil {
   342  			return "", err
   343  		}
   344  		defer unlock()
   345  
   346  		if err := f.downloadZip(ctx, mod, zipfile); err != nil {
   347  			return "", err
   348  		}
   349  		return zipfile, nil
   350  	})
   351  }
   352  
   353  func (f *Fetcher) downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) {
   354  	ctx, span := trace.StartSpan(ctx, "modfetch.downloadZip "+zipfile)
   355  	defer span.Done()
   356  
   357  	// Double-check that the zipfile was not created while we were waiting for
   358  	// the lock in DownloadZip.
   359  	ziphashfile := zipfile + "hash"
   360  	var zipExists, ziphashExists bool
   361  	if _, err := os.Stat(zipfile); err == nil {
   362  		zipExists = true
   363  	}
   364  	if _, err := os.Stat(ziphashfile); err == nil {
   365  		ziphashExists = true
   366  	}
   367  	if zipExists && ziphashExists {
   368  		return nil
   369  	}
   370  
   371  	// Create parent directories.
   372  	if err := os.MkdirAll(filepath.Dir(zipfile), 0o777); err != nil {
   373  		return err
   374  	}
   375  
   376  	// Clean up any remaining tempfiles from previous runs.
   377  	// This is only safe to do because the lock file ensures that their
   378  	// writers are no longer active.
   379  	tmpPattern := filepath.Base(zipfile) + "*.tmp"
   380  	if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(filepath.Dir(zipfile)), tmpPattern)); err == nil {
   381  		for _, path := range old {
   382  			os.Remove(path) // best effort
   383  		}
   384  	}
   385  
   386  	// If the zip file exists, the ziphash file must have been deleted
   387  	// or lost after a file system crash. Re-hash the zip without downloading.
   388  	if zipExists {
   389  		return hashZip(f, mod, zipfile, ziphashfile)
   390  	}
   391  
   392  	// From here to the os.Rename call below is functionally almost equivalent to
   393  	// renameio.WriteToFile, with one key difference: we want to validate the
   394  	// contents of the file (by hashing it) before we commit it. Because the file
   395  	// is zip-compressed, we need an actual file — or at least an io.ReaderAt — to
   396  	// validate it: we can't just tee the stream as we write it.
   397  	file, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666)
   398  	if err != nil {
   399  		return err
   400  	}
   401  	defer func() {
   402  		if err != nil {
   403  			file.Close()
   404  			os.Remove(file.Name())
   405  		}
   406  	}()
   407  
   408  	var unrecoverableErr error
   409  	err = TryProxies(func(proxy string) error {
   410  		if unrecoverableErr != nil {
   411  			return unrecoverableErr
   412  		}
   413  		repo := f.Lookup(ctx, proxy, mod.Path)
   414  		err := repo.Zip(ctx, file, mod.Version)
   415  		if err != nil {
   416  			// Zip may have partially written to f before failing.
   417  			// (Perhaps the server crashed while sending the file?)
   418  			// Since we allow fallback on error in some cases, we need to fix up the
   419  			// file to be empty again for the next attempt.
   420  			if _, err := file.Seek(0, io.SeekStart); err != nil {
   421  				unrecoverableErr = err
   422  				return err
   423  			}
   424  			if err := file.Truncate(0); err != nil {
   425  				unrecoverableErr = err
   426  				return err
   427  			}
   428  		}
   429  		return err
   430  	})
   431  	if err != nil {
   432  		return err
   433  	}
   434  
   435  	// Double-check that the paths within the zip file are well-formed.
   436  	//
   437  	// TODO(bcmills): There is a similar check within the Unzip function. Can we eliminate one?
   438  	fi, err := file.Stat()
   439  	if err != nil {
   440  		return err
   441  	}
   442  	z, err := zip.NewReader(file, fi.Size())
   443  	if err != nil {
   444  		return err
   445  	}
   446  	prefix := mod.Path + "@" + mod.Version + "/"
   447  	for _, zf := range z.File {
   448  		if !strings.HasPrefix(zf.Name, prefix) {
   449  			return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], zf.Name)
   450  		}
   451  	}
   452  
   453  	if err := file.Close(); err != nil {
   454  		return err
   455  	}
   456  
   457  	// Hash the zip file and check the sum before renaming to the final location.
   458  	if err := hashZip(f, mod, file.Name(), ziphashfile); err != nil {
   459  		return err
   460  	}
   461  	if err := os.Rename(file.Name(), zipfile); err != nil {
   462  		return err
   463  	}
   464  
   465  	// TODO(bcmills): Should we make the .zip and .ziphash files read-only to discourage tampering?
   466  
   467  	return nil
   468  }
   469  
   470  // hashZip reads the zip file opened in f, then writes the hash to ziphashfile,
   471  // overwriting that file if it exists.
   472  //
   473  // If the hash does not match go.sum (or the sumdb if enabled), hashZip returns
   474  // an error and does not write ziphashfile.
   475  func hashZip(f *Fetcher, mod module.Version, zipfile, ziphashfile string) (err error) {
   476  	hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash)
   477  	if err != nil {
   478  		return err
   479  	}
   480  	if err := checkModSum(f, mod, hash); err != nil {
   481  		return err
   482  	}
   483  	hf, err := lockedfile.Create(ziphashfile)
   484  	if err != nil {
   485  		return err
   486  	}
   487  	defer func() {
   488  		if closeErr := hf.Close(); err == nil && closeErr != nil {
   489  			err = closeErr
   490  		}
   491  	}()
   492  	if err := hf.Truncate(int64(len(hash))); err != nil {
   493  		return err
   494  	}
   495  	if _, err := hf.WriteAt([]byte(hash), 0); err != nil {
   496  		return err
   497  	}
   498  	return nil
   499  }
   500  
   501  // makeDirsReadOnly makes a best-effort attempt to remove write permissions for dir
   502  // and its transitive contents.
   503  func makeDirsReadOnly(dir string) {
   504  	type pathMode struct {
   505  		path string
   506  		mode fs.FileMode
   507  	}
   508  	var dirs []pathMode // in lexical order
   509  	filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
   510  		if err == nil && d.IsDir() {
   511  			info, err := d.Info()
   512  			if err == nil && info.Mode()&0o222 != 0 {
   513  				dirs = append(dirs, pathMode{path, info.Mode()})
   514  			}
   515  		}
   516  		return nil
   517  	})
   518  
   519  	// Run over list backward to chmod children before parents.
   520  	for i := len(dirs) - 1; i >= 0; i-- {
   521  		os.Chmod(dirs[i].path, dirs[i].mode&^0o222)
   522  	}
   523  }
   524  
   525  // RemoveAll removes a directory written by Download or Unzip, first applying
   526  // any permission changes needed to do so.
   527  func RemoveAll(dir string) error {
   528  	// Module cache has 0555 directories; make them writable in order to remove content.
   529  	filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
   530  		if err != nil {
   531  			return nil // ignore errors walking in file system
   532  		}
   533  		if info.IsDir() {
   534  			os.Chmod(path, 0o777)
   535  		}
   536  		return nil
   537  	})
   538  	return robustio.RemoveAll(dir)
   539  }
   540  
   541  // The GoSumFile, WorkspaceGoSumFiles, and goSum are global state that must not be
   542  // accessed by any of the exported functions of this package after they return, because
   543  // they can be modified by the non-thread-safe SetState function.
   544  
   545  type modSum struct {
   546  	mod module.Version
   547  	sum string
   548  }
   549  
   550  type sumState struct {
   551  	m         map[module.Version][]string            // content of go.sum file
   552  	w         map[string]map[module.Version][]string // sum file in workspace -> content of that sum file
   553  	status    map[modSum]modSumStatus                // state of sums in m
   554  	overwrite bool                                   // if true, overwrite go.sum without incorporating its contents
   555  	enabled   bool                                   // whether to use go.sum at all
   556  }
   557  
   558  type modSumStatus struct {
   559  	used, dirty bool
   560  }
   561  
   562  // Fetcher holds a snapshot of the global state of the modfetch package.
   563  type Fetcher struct {
   564  	// path to go.sum; set by package modload
   565  	goSumFile string
   566  	// path to module go.sums in workspace; set by package modload
   567  	workspaceGoSumFiles []string
   568  	// The Lookup cache is used cache the work done by Lookup.
   569  	// It is important that the global functions of this package that access it do not
   570  	// do so after they return.
   571  	lookupCache *par.Cache[lookupCacheKey, Repo]
   572  	// The downloadCache is used to cache the operation of downloading a module to disk
   573  	// (if it's not already downloaded) and getting the directory it was downloaded to.
   574  	// It is important that downloadCache must not be accessed by any of the exported
   575  	// functions of this package after they return, because it can be modified by the
   576  	// non-thread-safe SetState function.
   577  	downloadCache *par.ErrCache[module.Version, string] // version → directory;
   578  
   579  	mu       sync.Mutex
   580  	sumState sumState
   581  }
   582  
   583  func NewFetcher() *Fetcher {
   584  	f := new(Fetcher)
   585  	f.lookupCache = new(par.Cache[lookupCacheKey, Repo])
   586  	f.downloadCache = new(par.ErrCache[module.Version, string])
   587  	return f
   588  }
   589  
   590  func (f *Fetcher) GoSumFile() string {
   591  	return f.goSumFile
   592  }
   593  
   594  func (f *Fetcher) SetGoSumFile(str string) {
   595  	f.goSumFile = str
   596  }
   597  
   598  func (f *Fetcher) AddWorkspaceGoSumFile(file string) {
   599  	f.workspaceGoSumFiles = append(f.workspaceGoSumFiles, file)
   600  }
   601  
   602  // Reset resets globals in the modfetch package, so previous loads don't affect
   603  // contents of go.sum files.
   604  func (f *Fetcher) Reset() {
   605  	f.SetState(NewFetcher())
   606  }
   607  
   608  // SetState sets the global state of the modfetch package to the newState, and returns the previous
   609  // global state. newState should have been returned by SetState, or be an empty State.
   610  // There should be no concurrent calls to any of the exported functions of this package with
   611  // a call to SetState because it will modify the global state in a non-thread-safe way.
   612  func (f *Fetcher) SetState(newState *Fetcher) (oldState *Fetcher) {
   613  	if newState.lookupCache == nil {
   614  		newState.lookupCache = new(par.Cache[lookupCacheKey, Repo])
   615  	}
   616  	if newState.downloadCache == nil {
   617  		newState.downloadCache = new(par.ErrCache[module.Version, string])
   618  	}
   619  
   620  	f.mu.Lock()
   621  	defer f.mu.Unlock()
   622  
   623  	oldState = &Fetcher{
   624  		goSumFile:           f.goSumFile,
   625  		workspaceGoSumFiles: f.workspaceGoSumFiles,
   626  		lookupCache:         f.lookupCache,
   627  		downloadCache:       f.downloadCache,
   628  		sumState:            f.sumState,
   629  	}
   630  
   631  	f.SetGoSumFile(newState.goSumFile)
   632  	f.workspaceGoSumFiles = newState.workspaceGoSumFiles
   633  	// Uses of lookupCache and downloadCache both can call checkModSum,
   634  	// which in turn sets the used bit on goSum.status for modules.
   635  	// Set (or reset) them so used can be computed properly.
   636  	f.lookupCache = newState.lookupCache
   637  	f.downloadCache = newState.downloadCache
   638  	// Set, or reset all fields on goSum. If being reset to empty, it will be initialized later.
   639  	f.sumState = newState.sumState
   640  
   641  	return oldState
   642  }
   643  
   644  // initGoSum initializes the go.sum data.
   645  // The boolean it returns reports whether the
   646  // use of go.sum is now enabled.
   647  // The goSum lock must be held.
   648  func (f *Fetcher) initGoSum() (bool, error) {
   649  	if f.goSumFile == "" {
   650  		return false, nil
   651  	}
   652  	if f.sumState.m != nil {
   653  		return true, nil
   654  	}
   655  
   656  	f.sumState.m = make(map[module.Version][]string)
   657  	f.sumState.status = make(map[modSum]modSumStatus)
   658  	f.sumState.w = make(map[string]map[module.Version][]string)
   659  
   660  	for _, fn := range f.workspaceGoSumFiles {
   661  		f.sumState.w[fn] = make(map[module.Version][]string)
   662  		_, err := readGoSumFile(f.sumState.w[fn], fn)
   663  		if err != nil {
   664  			return false, err
   665  		}
   666  	}
   667  
   668  	enabled, err := readGoSumFile(f.sumState.m, f.goSumFile)
   669  	f.sumState.enabled = enabled
   670  	return enabled, err
   671  }
   672  
   673  func readGoSumFile(dst map[module.Version][]string, file string) (bool, error) {
   674  	var (
   675  		data []byte
   676  		err  error
   677  	)
   678  	if fsys.Replaced(file) {
   679  		// Don't lock go.sum if it's part of the overlay.
   680  		// On Plan 9, locking requires chmod, and we don't want to modify any file
   681  		// in the overlay. See #44700.
   682  		data, err = os.ReadFile(fsys.Actual(file))
   683  	} else {
   684  		data, err = lockedfile.Read(file)
   685  	}
   686  	if err != nil && !os.IsNotExist(err) {
   687  		return false, err
   688  	}
   689  	readGoSum(dst, file, data)
   690  
   691  	return true, nil
   692  }
   693  
   694  // emptyGoModHash is the hash of a 1-file tree containing a 0-length go.mod.
   695  // A bug caused us to write these into go.sum files for non-modules.
   696  // We detect and remove them.
   697  const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="
   698  
   699  // readGoSum parses data, which is the content of file,
   700  // and adds it to goSum.m. The goSum lock must be held.
   701  func readGoSum(dst map[module.Version][]string, file string, data []byte) {
   702  	lineno := 0
   703  	for len(data) > 0 {
   704  		var line []byte
   705  		lineno++
   706  		i := bytes.IndexByte(data, '\n')
   707  		if i < 0 {
   708  			line, data = data, nil
   709  		} else {
   710  			line, data = data[:i], data[i+1:]
   711  		}
   712  		f := strings.Fields(string(line))
   713  		if len(f) == 0 {
   714  			// blank line; skip it
   715  			continue
   716  		}
   717  		if len(f) != 3 {
   718  			if cfg.CmdName == "mod tidy" {
   719  				// ignore malformed line so that go mod tidy can fix go.sum
   720  				continue
   721  			} else {
   722  				base.Fatalf("malformed go.sum:\n%s:%d: wrong number of fields %v\n", file, lineno, len(f))
   723  			}
   724  		}
   725  		if f[2] == emptyGoModHash {
   726  			// Old bug; drop it.
   727  			continue
   728  		}
   729  		mod := module.Version{Path: f[0], Version: f[1]}
   730  		dst[mod] = append(dst[mod], f[2])
   731  	}
   732  }
   733  
   734  // HaveSum returns true if the go.sum file contains an entry for mod.
   735  // The entry's hash must be generated with a known hash algorithm.
   736  // mod.Version may have a "/go.mod" suffix to distinguish sums for
   737  // .mod and .zip files.
   738  func HaveSum(f *Fetcher, mod module.Version) bool {
   739  	f.mu.Lock()
   740  	defer f.mu.Unlock()
   741  	inited, err := f.initGoSum()
   742  	if err != nil || !inited {
   743  		return false
   744  	}
   745  	for _, goSums := range f.sumState.w {
   746  		for _, h := range goSums[mod] {
   747  			if !strings.HasPrefix(h, "h1:") {
   748  				continue
   749  			}
   750  			if !f.sumState.status[modSum{mod, h}].dirty {
   751  				return true
   752  			}
   753  		}
   754  	}
   755  	for _, h := range f.sumState.m[mod] {
   756  		if !strings.HasPrefix(h, "h1:") {
   757  			continue
   758  		}
   759  		if !f.sumState.status[modSum{mod, h}].dirty {
   760  			return true
   761  		}
   762  	}
   763  	return false
   764  }
   765  
   766  // RecordedSum returns the sum if the go.sum file contains an entry for mod.
   767  // The boolean reports true if an entry was found or
   768  // false if no entry found or two conflicting sums are found.
   769  // The entry's hash must be generated with a known hash algorithm.
   770  // mod.Version may have a "/go.mod" suffix to distinguish sums for
   771  // .mod and .zip files.
   772  func (f *Fetcher) RecordedSum(mod module.Version) (sum string, ok bool) {
   773  	f.mu.Lock()
   774  	defer f.mu.Unlock()
   775  	inited, err := f.initGoSum()
   776  	foundSum := ""
   777  	if err != nil || !inited {
   778  		return "", false
   779  	}
   780  	for _, goSums := range f.sumState.w {
   781  		for _, h := range goSums[mod] {
   782  			if !strings.HasPrefix(h, "h1:") {
   783  				continue
   784  			}
   785  			if !f.sumState.status[modSum{mod, h}].dirty {
   786  				if foundSum != "" && foundSum != h { // conflicting sums exist
   787  					return "", false
   788  				}
   789  				foundSum = h
   790  			}
   791  		}
   792  	}
   793  	for _, h := range f.sumState.m[mod] {
   794  		if !strings.HasPrefix(h, "h1:") {
   795  			continue
   796  		}
   797  		if !f.sumState.status[modSum{mod, h}].dirty {
   798  			if foundSum != "" && foundSum != h { // conflicting sums exist
   799  				return "", false
   800  			}
   801  			foundSum = h
   802  		}
   803  	}
   804  	return foundSum, true
   805  }
   806  
   807  // checkMod checks the given module's checksum and Go version.
   808  func (f *Fetcher) checkMod(ctx context.Context, mod module.Version) {
   809  	// Do the file I/O before acquiring the go.sum lock.
   810  	ziphash, err := CachePath(ctx, mod, "ziphash")
   811  	if err != nil {
   812  		base.Fatalf("verifying %v", module.VersionError(mod, err))
   813  	}
   814  	data, err := lockedfile.Read(ziphash)
   815  	if err != nil {
   816  		base.Fatalf("verifying %v", module.VersionError(mod, err))
   817  	}
   818  	data = bytes.TrimSpace(data)
   819  	if !isValidSum(data) {
   820  		// Recreate ziphash file from zip file and use that to check the mod sum.
   821  		zip, err := CachePath(ctx, mod, "zip")
   822  		if err != nil {
   823  			base.Fatalf("verifying %v", module.VersionError(mod, err))
   824  		}
   825  		err = hashZip(f, mod, zip, ziphash)
   826  		if err != nil {
   827  			base.Fatalf("verifying %v", module.VersionError(mod, err))
   828  		}
   829  		return
   830  	}
   831  	h := string(data)
   832  	if !strings.HasPrefix(h, "h1:") {
   833  		base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h)))
   834  	}
   835  
   836  	if err := checkModSum(f, mod, h); err != nil {
   837  		base.Fatalf("%s", err)
   838  	}
   839  }
   840  
   841  // goModSum returns the checksum for the go.mod contents.
   842  func goModSum(data []byte) (string, error) {
   843  	return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
   844  		return io.NopCloser(bytes.NewReader(data)), nil
   845  	})
   846  }
   847  
   848  // checkGoMod checks the given module's go.mod checksum;
   849  // data is the go.mod content.
   850  func checkGoMod(f *Fetcher, path, version string, data []byte) error {
   851  	h, err := goModSum(data)
   852  	if err != nil {
   853  		return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)}
   854  	}
   855  
   856  	return checkModSum(f, module.Version{Path: path, Version: version + "/go.mod"}, h)
   857  }
   858  
   859  // checkModSum checks that the recorded checksum for mod is h.
   860  //
   861  // mod.Version may have the additional suffix "/go.mod" to request the checksum
   862  // for the module's go.mod file only.
   863  func checkModSum(f *Fetcher, mod module.Version, h string) error {
   864  	// We lock goSum when manipulating it,
   865  	// but we arrange to release the lock when calling checkSumDB,
   866  	// so that parallel calls to checkModHash can execute parallel calls
   867  	// to checkSumDB.
   868  
   869  	// Check whether mod+h is listed in go.sum already. If so, we're done.
   870  	f.mu.Lock()
   871  	inited, err := f.initGoSum()
   872  	if err != nil {
   873  		f.mu.Unlock()
   874  		return err
   875  	}
   876  	done := inited && haveModSumLocked(f, mod, h)
   877  	if inited {
   878  		st := f.sumState.status[modSum{mod, h}]
   879  		st.used = true
   880  		f.sumState.status[modSum{mod, h}] = st
   881  	}
   882  	f.mu.Unlock()
   883  
   884  	if done && mod.Path != "golang.org/toolchain" {
   885  		return nil
   886  	}
   887  
   888  	// Consult checksum database if appropriate.
   889  	if useSumDB(mod) {
   890  		// Calls base.Fatalf if mismatch detected.
   891  		if err := checkSumDB(mod, h); err != nil {
   892  			return err
   893  		}
   894  	}
   895  
   896  	if done {
   897  		return nil
   898  	}
   899  
   900  	// Add mod+h to go.sum, if it hasn't appeared already.
   901  	if inited {
   902  		f.mu.Lock()
   903  		addModSumLocked(f, mod, h)
   904  		st := f.sumState.status[modSum{mod, h}]
   905  		st.dirty = true
   906  		f.sumState.status[modSum{mod, h}] = st
   907  		f.mu.Unlock()
   908  	}
   909  	return nil
   910  }
   911  
   912  // haveModSumLocked reports whether the pair mod,h is already listed in go.sum.
   913  // If it finds a conflicting pair instead, it calls base.Fatalf.
   914  // goSum.mu must be locked.
   915  func haveModSumLocked(f *Fetcher, mod module.Version, h string) bool {
   916  	sumFileName := "go.sum"
   917  	if strings.HasSuffix(f.goSumFile, "go.work.sum") {
   918  		sumFileName = "go.work.sum"
   919  	}
   920  	for _, vh := range f.sumState.m[mod] {
   921  		if h == vh {
   922  			return true
   923  		}
   924  		if strings.HasPrefix(vh, "h1:") {
   925  			base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s:     %v"+goSumMismatch, mod.Path, mod.Version, h, sumFileName, vh)
   926  		}
   927  	}
   928  	// Also check workspace sums.
   929  	foundMatch := false
   930  	// Check sums from all files in case there are conflicts between
   931  	// the files.
   932  	for goSumFile, goSums := range f.sumState.w {
   933  		for _, vh := range goSums[mod] {
   934  			if h == vh {
   935  				foundMatch = true
   936  			} else if strings.HasPrefix(vh, "h1:") {
   937  				base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s:     %v"+goSumMismatch, mod.Path, mod.Version, h, goSumFile, vh)
   938  			}
   939  		}
   940  	}
   941  	return foundMatch
   942  }
   943  
   944  // addModSumLocked adds the pair mod,h to go.sum.
   945  // goSum.mu must be locked.
   946  func addModSumLocked(f *Fetcher, mod module.Version, h string) {
   947  	if haveModSumLocked(f, mod, h) {
   948  		return
   949  	}
   950  	if len(f.sumState.m[mod]) > 0 {
   951  		fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(f.sumState.m[mod], ", "), h)
   952  	}
   953  	f.sumState.m[mod] = append(f.sumState.m[mod], h)
   954  }
   955  
   956  // checkSumDB checks the mod, h pair against the Go checksum database.
   957  // It calls base.Fatalf if the hash is to be rejected.
   958  func checkSumDB(mod module.Version, h string) error {
   959  	modWithoutSuffix := mod
   960  	noun := "module"
   961  	if before, found := strings.CutSuffix(mod.Version, "/go.mod"); found {
   962  		noun = "go.mod"
   963  		modWithoutSuffix.Version = before
   964  	}
   965  
   966  	db, lines, err := lookupSumDB(mod)
   967  	if err != nil {
   968  		return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: %v", noun, err))
   969  	}
   970  
   971  	have := mod.Path + " " + mod.Version + " " + h
   972  	prefix := mod.Path + " " + mod.Version + " h1:"
   973  	for _, line := range lines {
   974  		if line == have {
   975  			return nil
   976  		}
   977  		if strings.HasPrefix(line, prefix) {
   978  			return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+sumdbMismatch, noun, h, db, line[len(prefix)-len("h1:"):]))
   979  		}
   980  	}
   981  	return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum missing from sumdb response"+sumdbAbsent, noun))
   982  }
   983  
   984  // Sum returns the checksum for the downloaded copy of the given module,
   985  // if present in the download cache.
   986  func Sum(ctx context.Context, mod module.Version) string {
   987  	if cfg.GOMODCACHE == "" {
   988  		// Do not use current directory.
   989  		return ""
   990  	}
   991  
   992  	ziphash, err := CachePath(ctx, mod, "ziphash")
   993  	if err != nil {
   994  		return ""
   995  	}
   996  	data, err := lockedfile.Read(ziphash)
   997  	if err != nil {
   998  		return ""
   999  	}
  1000  	data = bytes.TrimSpace(data)
  1001  	if !isValidSum(data) {
  1002  		return ""
  1003  	}
  1004  	return string(data)
  1005  }
  1006  
  1007  // isValidSum returns true if data is the valid contents of a zip hash file.
  1008  // Certain critical files are written to disk by first truncating
  1009  // then writing the actual bytes, so that if the write fails
  1010  // the corrupt file should contain at least one of the null
  1011  // bytes written by the truncate operation.
  1012  func isValidSum(data []byte) bool {
  1013  	if bytes.IndexByte(data, '\000') >= 0 {
  1014  		return false
  1015  	}
  1016  
  1017  	if len(data) != len("h1:")+base64.StdEncoding.EncodedLen(sha256.Size) {
  1018  		return false
  1019  	}
  1020  
  1021  	return true
  1022  }
  1023  
  1024  var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=readonly")
  1025  
  1026  // WriteGoSum writes the go.sum file if it needs to be updated.
  1027  //
  1028  // keep is used to check whether a newly added sum should be saved in go.sum.
  1029  // It should have entries for both module content sums and go.mod sums
  1030  // (version ends with "/go.mod"). Existing sums will be preserved unless they
  1031  // have been marked for deletion with TrimGoSum.
  1032  func (f *Fetcher) WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error {
  1033  	f.mu.Lock()
  1034  	defer f.mu.Unlock()
  1035  
  1036  	// If we haven't read the go.sum file yet, don't bother writing it.
  1037  	if !f.sumState.enabled {
  1038  		return nil
  1039  	}
  1040  
  1041  	// Check whether we need to add sums for which keep[m] is true or remove
  1042  	// unused sums marked with TrimGoSum. If there are no changes to make,
  1043  	// just return without opening go.sum.
  1044  	dirty := false
  1045  Outer:
  1046  	for m, hs := range f.sumState.m {
  1047  		for _, h := range hs {
  1048  			st := f.sumState.status[modSum{m, h}]
  1049  			if st.dirty && (!st.used || keep[m]) {
  1050  				dirty = true
  1051  				break Outer
  1052  			}
  1053  		}
  1054  	}
  1055  	if !dirty {
  1056  		return nil
  1057  	}
  1058  	if readonly {
  1059  		return ErrGoSumDirty
  1060  	}
  1061  	if fsys.Replaced(f.goSumFile) {
  1062  		base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
  1063  	}
  1064  
  1065  	// Make a best-effort attempt to acquire the side lock, only to exclude
  1066  	// previous versions of the 'go' command from making simultaneous edits.
  1067  	if unlock, err := SideLock(ctx); err == nil {
  1068  		defer unlock()
  1069  	}
  1070  
  1071  	err := lockedfile.Transform(f.goSumFile, func(data []byte) ([]byte, error) {
  1072  		tidyGoSum := tidyGoSum(f, data, keep)
  1073  		return tidyGoSum, nil
  1074  	})
  1075  	if err != nil {
  1076  		return fmt.Errorf("updating go.sum: %w", err)
  1077  	}
  1078  
  1079  	f.sumState.status = make(map[modSum]modSumStatus)
  1080  	f.sumState.overwrite = false
  1081  	return nil
  1082  }
  1083  
  1084  // TidyGoSum returns a tidy version of the go.sum file.
  1085  // A missing go.sum file is treated as if empty.
  1086  func (f *Fetcher) TidyGoSum(keep map[module.Version]bool) (before, after []byte) {
  1087  	f.mu.Lock()
  1088  	defer f.mu.Unlock()
  1089  	before, err := lockedfile.Read(f.goSumFile)
  1090  	if err != nil && !errors.Is(err, fs.ErrNotExist) {
  1091  		base.Fatalf("reading go.sum: %v", err)
  1092  	}
  1093  	after = tidyGoSum(f, before, keep)
  1094  	return before, after
  1095  }
  1096  
  1097  // tidyGoSum returns a tidy version of the go.sum file.
  1098  // The goSum lock must be held.
  1099  func tidyGoSum(f *Fetcher, data []byte, keep map[module.Version]bool) []byte {
  1100  	if !f.sumState.overwrite {
  1101  		// Incorporate any sums added by other processes in the meantime.
  1102  		// Add only the sums that we actually checked: the user may have edited or
  1103  		// truncated the file to remove erroneous hashes, and we shouldn't restore
  1104  		// them without good reason.
  1105  		f.sumState.m = make(map[module.Version][]string, len(f.sumState.m))
  1106  		readGoSum(f.sumState.m, f.goSumFile, data)
  1107  		for ms, st := range f.sumState.status {
  1108  			if st.used && !sumInWorkspaceModulesLocked(f, ms.mod) {
  1109  				addModSumLocked(f, ms.mod, ms.sum)
  1110  			}
  1111  		}
  1112  	}
  1113  
  1114  	mods := make([]module.Version, 0, len(f.sumState.m))
  1115  	for m := range f.sumState.m {
  1116  		mods = append(mods, m)
  1117  	}
  1118  	module.Sort(mods)
  1119  
  1120  	var buf bytes.Buffer
  1121  	for _, m := range mods {
  1122  		list := f.sumState.m[m]
  1123  		sort.Strings(list)
  1124  		str.Uniq(&list)
  1125  		for _, h := range list {
  1126  			st := f.sumState.status[modSum{m, h}]
  1127  			if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(f, m) {
  1128  				fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
  1129  			}
  1130  		}
  1131  	}
  1132  	return buf.Bytes()
  1133  }
  1134  
  1135  func sumInWorkspaceModulesLocked(f *Fetcher, m module.Version) bool {
  1136  	for _, goSums := range f.sumState.w {
  1137  		if _, ok := goSums[m]; ok {
  1138  			return true
  1139  		}
  1140  	}
  1141  	return false
  1142  }
  1143  
  1144  // TrimGoSum trims go.sum to contain only the modules needed for reproducible
  1145  // builds.
  1146  //
  1147  // keep is used to check whether a sum should be retained in go.mod. It should
  1148  // have entries for both module content sums and go.mod sums (version ends
  1149  // with "/go.mod").
  1150  func (f *Fetcher) TrimGoSum(keep map[module.Version]bool) {
  1151  	f.mu.Lock()
  1152  	defer f.mu.Unlock()
  1153  	inited, err := f.initGoSum()
  1154  	if err != nil {
  1155  		base.Fatalf("%s", err)
  1156  	}
  1157  	if !inited {
  1158  		return
  1159  	}
  1160  
  1161  	for m, hs := range f.sumState.m {
  1162  		if !keep[m] {
  1163  			for _, h := range hs {
  1164  				f.sumState.status[modSum{m, h}] = modSumStatus{used: false, dirty: true}
  1165  			}
  1166  			f.sumState.overwrite = true
  1167  		}
  1168  	}
  1169  }
  1170  
  1171  const goSumMismatch = `
  1172  
  1173  SECURITY ERROR
  1174  This download does NOT match an earlier download recorded in go.sum.
  1175  The bits may have been replaced on the origin server, or an attacker may
  1176  have intercepted the download attempt.
  1177  
  1178  For more information, see 'go help module-auth'.
  1179  `
  1180  
  1181  const sumdbMismatch = `
  1182  
  1183  SECURITY ERROR
  1184  This download does NOT match the one reported by the checksum server.
  1185  The bits may have been replaced on the origin server, or an attacker may
  1186  have intercepted the download attempt.
  1187  
  1188  For more information, see 'go help module-auth'.
  1189  `
  1190  
  1191  const sumdbAbsent = `
  1192  
  1193  SECURITY ERROR
  1194  This download does NOT match one reported by the checksum server.
  1195  The checksum server has provided checksums, but the checksums do
  1196  not contain an entry for the download.
  1197  The checksum server may be malfunctioning, or an attacker may have
  1198  intercepted the checksum request.
  1199  The download cannot be verified.
  1200  
  1201  For more information, see 'go help module-auth'.
  1202  `
  1203  
  1204  const hashVersionMismatch = `
  1205  
  1206  SECURITY WARNING
  1207  This download is listed in go.sum, but using an unknown hash algorithm.
  1208  The download cannot be verified.
  1209  
  1210  For more information, see 'go help module-auth'.
  1211  
  1212  `
  1213  
  1214  var HelpModuleAuth = &base.Command{
  1215  	UsageLine: "module-auth",
  1216  	Short:     "module authentication using go.sum",
  1217  	Long: `
  1218  When the go command downloads a module zip file or go.mod file into the
  1219  module cache, it computes a cryptographic hash and compares it with a known
  1220  value to verify the file hasn't changed since it was first downloaded. Known
  1221  hashes are stored in a file in the module root directory named go.sum. Hashes
  1222  may also be downloaded from the checksum database depending on the values of
  1223  GOSUMDB, GOPRIVATE, and GONOSUMDB.
  1224  
  1225  For details, see https://go.dev/ref/mod#authenticating.
  1226  `,
  1227  }
  1228  
  1229  var HelpPrivate = &base.Command{
  1230  	UsageLine: "private",
  1231  	Short:     "configuration for downloading non-public code",
  1232  	Long: `
  1233  The go command defaults to downloading modules from the public Go module
  1234  mirror at proxy.golang.org. It also defaults to validating downloaded modules,
  1235  regardless of source, against the public Go checksum database at sum.golang.org.
  1236  These defaults work well for publicly available source code.
  1237  
  1238  The GOPRIVATE environment variable controls which modules the go command
  1239  considers to be private (not available publicly) and should therefore not use
  1240  the proxy or checksum database. The variable is a comma-separated list of
  1241  glob patterns (in the syntax of Go's path.Match) of module path prefixes.
  1242  For example,
  1243  
  1244  	GOPRIVATE=*.corp.example.com,rsc.io/private
  1245  
  1246  causes the go command to treat as private any module with a path prefix
  1247  matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private,
  1248  and rsc.io/private/quux.
  1249  
  1250  For fine-grained control over module download and validation, the GONOPROXY
  1251  and GONOSUMDB environment variables accept the same kind of glob list
  1252  and override GOPRIVATE for the specific decision of whether to use the proxy
  1253  and checksum database, respectively.
  1254  
  1255  For example, if a company ran a module proxy serving private modules,
  1256  users would configure go using:
  1257  
  1258  	GOPRIVATE=*.corp.example.com
  1259  	GOPROXY=proxy.example.com
  1260  	GONOPROXY=none
  1261  
  1262  The GOPRIVATE variable is also used to define the "public" and "private"
  1263  patterns for the GOVCS variable; see 'go help vcs'. For that usage,
  1264  GOPRIVATE applies even in GOPATH mode. In that case, it matches import paths
  1265  instead of module paths.
  1266  
  1267  The 'go env -w' command (see 'go help env') can be used to set these variables
  1268  for future go command invocations.
  1269  
  1270  For more details, see https://go.dev/ref/mod#private-modules.
  1271  `,
  1272  }
  1273  

View as plain text