github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/modfetch/fetch.go (about)

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