github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/go/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  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"sort"
    16  	"strings"
    17  	"sync"
    18  
    19  	"cmd/go/internal/base"
    20  	"cmd/go/internal/cfg"
    21  	"cmd/go/internal/dirhash"
    22  	"cmd/go/internal/module"
    23  	"cmd/go/internal/par"
    24  )
    25  
    26  var downloadCache par.Cache
    27  
    28  // Download downloads the specific module version to the
    29  // local download cache and returns the name of the directory
    30  // corresponding to the root of the module's file tree.
    31  func Download(mod module.Version) (dir string, err error) {
    32  	if PkgMod == "" {
    33  		// Do not download to current directory.
    34  		return "", fmt.Errorf("missing modfetch.PkgMod")
    35  	}
    36  
    37  	// The par.Cache here avoids duplicate work but also
    38  	// avoids conflicts from simultaneous calls by multiple goroutines
    39  	// for the same version.
    40  	type cached struct {
    41  		dir string
    42  		err error
    43  	}
    44  	c := downloadCache.Do(mod, func() interface{} {
    45  		dir, err := DownloadDir(mod)
    46  		if err != nil {
    47  			return cached{"", err}
    48  		}
    49  		if files, _ := ioutil.ReadDir(dir); len(files) == 0 {
    50  			zipfile, err := DownloadZip(mod)
    51  			if err != nil {
    52  				return cached{"", err}
    53  			}
    54  			modpath := mod.Path + "@" + mod.Version
    55  			if err := Unzip(dir, zipfile, modpath, 0); err != nil {
    56  				fmt.Fprintf(os.Stderr, "-> %s\n", err)
    57  				return cached{"", err}
    58  			}
    59  		}
    60  		checkSum(mod)
    61  		return cached{dir, nil}
    62  	}).(cached)
    63  	return c.dir, c.err
    64  }
    65  
    66  var downloadZipCache par.Cache
    67  
    68  // DownloadZip downloads the specific module version to the
    69  // local zip cache and returns the name of the zip file.
    70  func DownloadZip(mod module.Version) (zipfile string, err error) {
    71  	// The par.Cache here avoids duplicate work but also
    72  	// avoids conflicts from simultaneous calls by multiple goroutines
    73  	// for the same version.
    74  	type cached struct {
    75  		zipfile string
    76  		err     error
    77  	}
    78  	c := downloadZipCache.Do(mod, func() interface{} {
    79  		zipfile, err := CachePath(mod, "zip")
    80  		if err != nil {
    81  			return cached{"", err}
    82  		}
    83  		if _, err := os.Stat(zipfile); err == nil {
    84  			// Use it.
    85  			// This should only happen if the mod/cache directory is preinitialized
    86  			// or if pkg/mod/path was removed but not pkg/mod/cache/download.
    87  			if cfg.CmdName != "mod download" {
    88  				fmt.Fprintf(os.Stderr, "go: extracting %s %s\n", mod.Path, mod.Version)
    89  			}
    90  		} else {
    91  			if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil {
    92  				return cached{"", err}
    93  			}
    94  			if cfg.CmdName != "mod download" {
    95  				fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, mod.Version)
    96  			}
    97  			if err := downloadZip(mod, zipfile); err != nil {
    98  				return cached{"", err}
    99  			}
   100  		}
   101  		return cached{zipfile, nil}
   102  	}).(cached)
   103  	return c.zipfile, c.err
   104  }
   105  
   106  func downloadZip(mod module.Version, target string) error {
   107  	repo, err := Lookup(mod.Path)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	tmpfile, err := repo.Zip(mod.Version, os.TempDir())
   112  	if err != nil {
   113  		return err
   114  	}
   115  	defer os.Remove(tmpfile)
   116  
   117  	// Double-check zip file looks OK.
   118  	z, err := zip.OpenReader(tmpfile)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	prefix := mod.Path + "@" + mod.Version
   123  	for _, f := range z.File {
   124  		if !strings.HasPrefix(f.Name, prefix) {
   125  			z.Close()
   126  			return fmt.Errorf("zip for %s has unexpected file %s", prefix, f.Name)
   127  		}
   128  	}
   129  	z.Close()
   130  
   131  	hash, err := dirhash.HashZip(tmpfile, dirhash.DefaultHash)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	checkOneSum(mod, hash) // check before installing the zip file
   136  	r, err := os.Open(tmpfile)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	defer r.Close()
   141  	w, err := os.Create(target)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	if _, err := io.Copy(w, r); err != nil {
   146  		w.Close()
   147  		return fmt.Errorf("copying: %v", err)
   148  	}
   149  	if err := w.Close(); err != nil {
   150  		return err
   151  	}
   152  	return ioutil.WriteFile(target+"hash", []byte(hash), 0666)
   153  }
   154  
   155  var GoSumFile string // path to go.sum; set by package modload
   156  
   157  var goSum struct {
   158  	mu        sync.Mutex
   159  	m         map[module.Version][]string // content of go.sum file (+ go.modverify if present)
   160  	enabled   bool                        // whether to use go.sum at all
   161  	modverify string                      // path to go.modverify, to be deleted
   162  }
   163  
   164  // initGoSum initializes the go.sum data.
   165  // It reports whether use of go.sum is now enabled.
   166  // The goSum lock must be held.
   167  func initGoSum() bool {
   168  	if GoSumFile == "" {
   169  		return false
   170  	}
   171  	if goSum.m != nil {
   172  		return true
   173  	}
   174  
   175  	goSum.m = make(map[module.Version][]string)
   176  	data, err := ioutil.ReadFile(GoSumFile)
   177  	if err != nil && !os.IsNotExist(err) {
   178  		base.Fatalf("go: %v", err)
   179  	}
   180  	goSum.enabled = true
   181  	readGoSum(GoSumFile, data)
   182  
   183  	// Add old go.modverify file.
   184  	// We'll delete go.modverify in WriteGoSum.
   185  	alt := strings.TrimSuffix(GoSumFile, ".sum") + ".modverify"
   186  	if data, err := ioutil.ReadFile(alt); err == nil {
   187  		readGoSum(alt, data)
   188  		goSum.modverify = alt
   189  	}
   190  	return true
   191  }
   192  
   193  // emptyGoModHash is the hash of a 1-file tree containing a 0-length go.mod.
   194  // A bug caused us to write these into go.sum files for non-modules.
   195  // We detect and remove them.
   196  const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="
   197  
   198  // readGoSum parses data, which is the content of file,
   199  // and adds it to goSum.m. The goSum lock must be held.
   200  func readGoSum(file string, data []byte) {
   201  	lineno := 0
   202  	for len(data) > 0 {
   203  		var line []byte
   204  		lineno++
   205  		i := bytes.IndexByte(data, '\n')
   206  		if i < 0 {
   207  			line, data = data, nil
   208  		} else {
   209  			line, data = data[:i], data[i+1:]
   210  		}
   211  		f := strings.Fields(string(line))
   212  		if len(f) == 0 {
   213  			// blank line; skip it
   214  			continue
   215  		}
   216  		if len(f) != 3 {
   217  			base.Fatalf("go: malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
   218  		}
   219  		if f[2] == emptyGoModHash {
   220  			// Old bug; drop it.
   221  			continue
   222  		}
   223  		mod := module.Version{Path: f[0], Version: f[1]}
   224  		goSum.m[mod] = append(goSum.m[mod], f[2])
   225  	}
   226  }
   227  
   228  // checkSum checks the given module's checksum.
   229  func checkSum(mod module.Version) {
   230  	if PkgMod == "" {
   231  		// Do not use current directory.
   232  		return
   233  	}
   234  
   235  	// Do the file I/O before acquiring the go.sum lock.
   236  	ziphash, err := CachePath(mod, "ziphash")
   237  	if err != nil {
   238  		base.Fatalf("go: verifying %s@%s: %v", mod.Path, mod.Version, err)
   239  	}
   240  	data, err := ioutil.ReadFile(ziphash)
   241  	if err != nil {
   242  		if os.IsNotExist(err) {
   243  			// This can happen if someone does rm -rf GOPATH/src/cache/download. So it goes.
   244  			return
   245  		}
   246  		base.Fatalf("go: verifying %s@%s: %v", mod.Path, mod.Version, err)
   247  	}
   248  	h := strings.TrimSpace(string(data))
   249  	if !strings.HasPrefix(h, "h1:") {
   250  		base.Fatalf("go: verifying %s@%s: unexpected ziphash: %q", mod.Path, mod.Version, h)
   251  	}
   252  
   253  	checkOneSum(mod, h)
   254  }
   255  
   256  // goModSum returns the checksum for the go.mod contents.
   257  func goModSum(data []byte) (string, error) {
   258  	return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
   259  		return ioutil.NopCloser(bytes.NewReader(data)), nil
   260  	})
   261  }
   262  
   263  // checkGoMod checks the given module's go.mod checksum;
   264  // data is the go.mod content.
   265  func checkGoMod(path, version string, data []byte) {
   266  	h, err := goModSum(data)
   267  	if err != nil {
   268  		base.Fatalf("go: verifying %s %s go.mod: %v", path, version, err)
   269  	}
   270  
   271  	checkOneSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
   272  }
   273  
   274  // checkOneSum checks that the recorded hash for mod is h.
   275  func checkOneSum(mod module.Version, h string) {
   276  	goSum.mu.Lock()
   277  	defer goSum.mu.Unlock()
   278  	if !initGoSum() {
   279  		return
   280  	}
   281  
   282  	for _, vh := range goSum.m[mod] {
   283  		if h == vh {
   284  			return
   285  		}
   286  		if strings.HasPrefix(vh, "h1:") {
   287  			base.Fatalf("go: verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tgo.sum:     %v", mod.Path, mod.Version, h, vh)
   288  		}
   289  	}
   290  	if len(goSum.m[mod]) > 0 {
   291  		fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v", mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h)
   292  	}
   293  	goSum.m[mod] = append(goSum.m[mod], h)
   294  }
   295  
   296  // Sum returns the checksum for the downloaded copy of the given module,
   297  // if present in the download cache.
   298  func Sum(mod module.Version) string {
   299  	if PkgMod == "" {
   300  		// Do not use current directory.
   301  		return ""
   302  	}
   303  
   304  	ziphash, err := CachePath(mod, "ziphash")
   305  	if err != nil {
   306  		return ""
   307  	}
   308  	data, err := ioutil.ReadFile(ziphash)
   309  	if err != nil {
   310  		return ""
   311  	}
   312  	return strings.TrimSpace(string(data))
   313  }
   314  
   315  // WriteGoSum writes the go.sum file if it needs to be updated.
   316  func WriteGoSum() {
   317  	goSum.mu.Lock()
   318  	defer goSum.mu.Unlock()
   319  	if !initGoSum() {
   320  		return
   321  	}
   322  
   323  	var mods []module.Version
   324  	for m := range goSum.m {
   325  		mods = append(mods, m)
   326  	}
   327  	module.Sort(mods)
   328  	var buf bytes.Buffer
   329  	for _, m := range mods {
   330  		list := goSum.m[m]
   331  		sort.Strings(list)
   332  		for _, h := range list {
   333  			fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
   334  		}
   335  	}
   336  
   337  	data, _ := ioutil.ReadFile(GoSumFile)
   338  	if !bytes.Equal(data, buf.Bytes()) {
   339  		if err := ioutil.WriteFile(GoSumFile, buf.Bytes(), 0666); err != nil {
   340  			base.Fatalf("go: writing go.sum: %v", err)
   341  		}
   342  	}
   343  
   344  	if goSum.modverify != "" {
   345  		os.Remove(goSum.modverify)
   346  	}
   347  }
   348  
   349  // TrimGoSum trims go.sum to contain only the modules for which keep[m] is true.
   350  func TrimGoSum(keep map[module.Version]bool) {
   351  	goSum.mu.Lock()
   352  	defer goSum.mu.Unlock()
   353  	if !initGoSum() {
   354  		return
   355  	}
   356  
   357  	for m := range goSum.m {
   358  		// If we're keeping x@v we also keep x@v/go.mod.
   359  		// Map x@v/go.mod back to x@v for the keep lookup.
   360  		noGoMod := module.Version{Path: m.Path, Version: strings.TrimSuffix(m.Version, "/go.mod")}
   361  		if !keep[m] && !keep[noGoMod] {
   362  			delete(goSum.m, m)
   363  		}
   364  	}
   365  }