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