github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-internal/modfetch/cache.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  	"bytes"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  
    18  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/base"
    19  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/cfg"
    20  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/lockedfile"
    21  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/modfetch/codehost"
    22  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/par"
    23  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/renameio"
    24  
    25  	"golang.org/x/mod/module"
    26  	"golang.org/x/mod/semver"
    27  )
    28  
    29  var PkgMod string // $GOPATH/pkg/mod; set by package modload
    30  
    31  func cacheDir(path string) (string, error) {
    32  	if PkgMod == "" {
    33  		return "", fmt.Errorf("internal error: modfetch.PkgMod not set")
    34  	}
    35  	enc, err := module.EscapePath(path)
    36  	if err != nil {
    37  		return "", err
    38  	}
    39  	return filepath.Join(PkgMod, "cache/download", enc, "/@v"), nil
    40  }
    41  
    42  func CachePath(m module.Version, suffix string) (string, error) {
    43  	dir, err := cacheDir(m.Path)
    44  	if err != nil {
    45  		return "", err
    46  	}
    47  	if !semver.IsValid(m.Version) {
    48  		return "", fmt.Errorf("non-semver module version %q", m.Version)
    49  	}
    50  	if module.CanonicalVersion(m.Version) != m.Version {
    51  		return "", fmt.Errorf("non-canonical module version %q", m.Version)
    52  	}
    53  	encVer, err := module.EscapeVersion(m.Version)
    54  	if err != nil {
    55  		return "", err
    56  	}
    57  	return filepath.Join(dir, encVer+"."+suffix), nil
    58  }
    59  
    60  // DownloadDir returns the directory to which m should have been downloaded.
    61  // An error will be returned if the module path or version cannot be escaped.
    62  // An error satisfying errors.Is(err, os.ErrNotExist) will be returned
    63  // along with the directory if the directory does not exist or if the directory
    64  // is not completely populated.
    65  func DownloadDir(m module.Version) (string, error) {
    66  	if PkgMod == "" {
    67  		return "", fmt.Errorf("internal error: modfetch.PkgMod not set")
    68  	}
    69  	enc, err := module.EscapePath(m.Path)
    70  	if err != nil {
    71  		return "", err
    72  	}
    73  	if !semver.IsValid(m.Version) {
    74  		return "", fmt.Errorf("non-semver module version %q", m.Version)
    75  	}
    76  	if module.CanonicalVersion(m.Version) != m.Version {
    77  		return "", fmt.Errorf("non-canonical module version %q", m.Version)
    78  	}
    79  	encVer, err := module.EscapeVersion(m.Version)
    80  	if err != nil {
    81  		return "", err
    82  	}
    83  
    84  	dir := filepath.Join(PkgMod, enc+"@"+encVer)
    85  	if fi, err := os.Stat(dir); os.IsNotExist(err) {
    86  		return dir, err
    87  	} else if err != nil {
    88  		return dir, &DownloadDirPartialError{dir, err}
    89  	} else if !fi.IsDir() {
    90  		return dir, &DownloadDirPartialError{dir, errors.New("not a directory")}
    91  	}
    92  	partialPath, err := CachePath(m, "partial")
    93  	if err != nil {
    94  		return dir, err
    95  	}
    96  	if _, err := os.Stat(partialPath); err == nil {
    97  		return dir, &DownloadDirPartialError{dir, errors.New("not completely extracted")}
    98  	} else if !os.IsNotExist(err) {
    99  		return dir, err
   100  	}
   101  	return dir, nil
   102  }
   103  
   104  // DownloadDirPartialError is returned by DownloadDir if a module directory
   105  // exists but was not completely populated.
   106  //
   107  // DownloadDirPartialError is equivalent to os.ErrNotExist.
   108  type DownloadDirPartialError struct {
   109  	Dir string
   110  	Err error
   111  }
   112  
   113  func (e *DownloadDirPartialError) Error() string     { return fmt.Sprintf("%s: %v", e.Dir, e.Err) }
   114  func (e *DownloadDirPartialError) Is(err error) bool { return err == os.ErrNotExist }
   115  
   116  // lockVersion locks a file within the module cache that guards the downloading
   117  // and extraction of the zipfile for the given module version.
   118  func lockVersion(mod module.Version) (unlock func(), err error) {
   119  	path, err := CachePath(mod, "lock")
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
   124  		return nil, err
   125  	}
   126  	return lockedfile.MutexAt(path).Lock()
   127  }
   128  
   129  // SideLock locks a file within the module cache that that previously guarded
   130  // edits to files outside the cache, such as go.sum and go.mod files in the
   131  // user's working directory.
   132  // If err is nil, the caller MUST eventually call the unlock function.
   133  func SideLock() (unlock func(), err error) {
   134  	if PkgMod == "" {
   135  		base.Fatalf("go: internal error: modfetch.PkgMod not set")
   136  	}
   137  
   138  	path := filepath.Join(PkgMod, "cache", "lock")
   139  	if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
   140  		return nil, fmt.Errorf("failed to create cache directory: %w", err)
   141  	}
   142  
   143  	return lockedfile.MutexAt(path).Lock()
   144  }
   145  
   146  // A cachingRepo is a cache around an underlying Repo,
   147  // avoiding redundant calls to ModulePath, Versions, Stat, Latest, and GoMod (but not Zip).
   148  // It is also safe for simultaneous use by multiple goroutines
   149  // (so that it can be returned from Lookup multiple times).
   150  // It serializes calls to the underlying Repo.
   151  type cachingRepo struct {
   152  	path  string
   153  	cache par.Cache // cache for all operations
   154  	r     Repo
   155  }
   156  
   157  func newCachingRepo(r Repo) *cachingRepo {
   158  	return &cachingRepo{
   159  		r:    r,
   160  		path: r.ModulePath(),
   161  	}
   162  }
   163  
   164  func (r *cachingRepo) ModulePath() string {
   165  	return r.path
   166  }
   167  
   168  func (r *cachingRepo) Versions(prefix string) ([]string, error) {
   169  	type cached struct {
   170  		list []string
   171  		err  error
   172  	}
   173  	c := r.cache.Do("versions:"+prefix, func() interface{} {
   174  		list, err := r.r.Versions(prefix)
   175  		return cached{list, err}
   176  	}).(cached)
   177  
   178  	if c.err != nil {
   179  		return nil, c.err
   180  	}
   181  	return append([]string(nil), c.list...), nil
   182  }
   183  
   184  type cachedInfo struct {
   185  	info *RevInfo
   186  	err  error
   187  }
   188  
   189  func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
   190  	c := r.cache.Do("stat:"+rev, func() interface{} {
   191  		file, info, err := readDiskStat(r.path, rev)
   192  		if err == nil {
   193  			return cachedInfo{info, nil}
   194  		}
   195  
   196  		info, err = r.r.Stat(rev)
   197  		if err == nil {
   198  			// If we resolved, say, 1234abcde to v0.0.0-20180604122334-1234abcdef78,
   199  			// then save the information under the proper version, for future use.
   200  			if info.Version != rev {
   201  				file, _ = CachePath(module.Version{Path: r.path, Version: info.Version}, "info")
   202  				r.cache.Do("stat:"+info.Version, func() interface{} {
   203  					return cachedInfo{info, err}
   204  				})
   205  			}
   206  
   207  			if err := writeDiskStat(file, info); err != nil {
   208  				fmt.Fprintf(os.Stderr, "go: writing stat cache: %v\n", err)
   209  			}
   210  		}
   211  		return cachedInfo{info, err}
   212  	}).(cachedInfo)
   213  
   214  	if c.err != nil {
   215  		return nil, c.err
   216  	}
   217  	info := *c.info
   218  	return &info, nil
   219  }
   220  
   221  func (r *cachingRepo) Latest() (*RevInfo, error) {
   222  	c := r.cache.Do("latest:", func() interface{} {
   223  		info, err := r.r.Latest()
   224  
   225  		// Save info for likely future Stat call.
   226  		if err == nil {
   227  			r.cache.Do("stat:"+info.Version, func() interface{} {
   228  				return cachedInfo{info, err}
   229  			})
   230  			if file, _, err := readDiskStat(r.path, info.Version); err != nil {
   231  				writeDiskStat(file, info)
   232  			}
   233  		}
   234  
   235  		return cachedInfo{info, err}
   236  	}).(cachedInfo)
   237  
   238  	if c.err != nil {
   239  		return nil, c.err
   240  	}
   241  	info := *c.info
   242  	return &info, nil
   243  }
   244  
   245  func (r *cachingRepo) GoMod(version string) ([]byte, error) {
   246  	type cached struct {
   247  		text []byte
   248  		err  error
   249  	}
   250  	c := r.cache.Do("gomod:"+version, func() interface{} {
   251  		file, text, err := readDiskGoMod(r.path, version)
   252  		if err == nil {
   253  			// Note: readDiskGoMod already called checkGoMod.
   254  			return cached{text, nil}
   255  		}
   256  
   257  		text, err = r.r.GoMod(version)
   258  		if err == nil {
   259  			if err := checkGoMod(r.path, version, text); err != nil {
   260  				return cached{text, err}
   261  			}
   262  			if err := writeDiskGoMod(file, text); err != nil {
   263  				fmt.Fprintf(os.Stderr, "go: writing go.mod cache: %v\n", err)
   264  			}
   265  		}
   266  		return cached{text, err}
   267  	}).(cached)
   268  
   269  	if c.err != nil {
   270  		return nil, c.err
   271  	}
   272  	return append([]byte(nil), c.text...), nil
   273  }
   274  
   275  func (r *cachingRepo) Zip(dst io.Writer, version string) error {
   276  	return r.r.Zip(dst, version)
   277  }
   278  
   279  // Stat is like Lookup(path).Stat(rev) but avoids the
   280  // repository path resolution in Lookup if the result is
   281  // already cached on local disk.
   282  func Stat(proxy, path, rev string) (*RevInfo, error) {
   283  	_, info, err := readDiskStat(path, rev)
   284  	if err == nil {
   285  		return info, nil
   286  	}
   287  	repo, err := Lookup(proxy, path)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	return repo.Stat(rev)
   292  }
   293  
   294  // InfoFile is like Stat but returns the name of the file containing
   295  // the cached information.
   296  func InfoFile(path, version string) (string, error) {
   297  	if !semver.IsValid(version) {
   298  		return "", fmt.Errorf("invalid version %q", version)
   299  	}
   300  
   301  	if file, _, err := readDiskStat(path, version); err == nil {
   302  		return file, nil
   303  	}
   304  
   305  	err := TryProxies(func(proxy string) error {
   306  		repo, err := Lookup(proxy, path)
   307  		if err == nil {
   308  			_, err = repo.Stat(version)
   309  		}
   310  		return err
   311  	})
   312  	if err != nil {
   313  		return "", err
   314  	}
   315  
   316  	// Stat should have populated the disk cache for us.
   317  	file, _, err := readDiskStat(path, version)
   318  	if err != nil {
   319  		return "", err
   320  	}
   321  	return file, nil
   322  }
   323  
   324  // GoMod is like Lookup(path).GoMod(rev) but avoids the
   325  // repository path resolution in Lookup if the result is
   326  // already cached on local disk.
   327  func GoMod(path, rev string) ([]byte, error) {
   328  	// Convert commit hash to pseudo-version
   329  	// to increase cache hit rate.
   330  	if !semver.IsValid(rev) {
   331  		if _, info, err := readDiskStat(path, rev); err == nil {
   332  			rev = info.Version
   333  		} else {
   334  			err := TryProxies(func(proxy string) error {
   335  				repo, err := Lookup(proxy, path)
   336  				if err != nil {
   337  					return err
   338  				}
   339  				info, err := repo.Stat(rev)
   340  				if err == nil {
   341  					rev = info.Version
   342  				}
   343  				return err
   344  			})
   345  			if err != nil {
   346  				return nil, err
   347  			}
   348  		}
   349  	}
   350  
   351  	_, data, err := readDiskGoMod(path, rev)
   352  	if err == nil {
   353  		return data, nil
   354  	}
   355  
   356  	err = TryProxies(func(proxy string) error {
   357  		repo, err := Lookup(proxy, path)
   358  		if err == nil {
   359  			data, err = repo.GoMod(rev)
   360  		}
   361  		return err
   362  	})
   363  	return data, err
   364  }
   365  
   366  // GoModFile is like GoMod but returns the name of the file containing
   367  // the cached information.
   368  func GoModFile(path, version string) (string, error) {
   369  	if !semver.IsValid(version) {
   370  		return "", fmt.Errorf("invalid version %q", version)
   371  	}
   372  	if _, err := GoMod(path, version); err != nil {
   373  		return "", err
   374  	}
   375  	// GoMod should have populated the disk cache for us.
   376  	file, _, err := readDiskGoMod(path, version)
   377  	if err != nil {
   378  		return "", err
   379  	}
   380  	return file, nil
   381  }
   382  
   383  // GoModSum returns the go.sum entry for the module version's go.mod file.
   384  // (That is, it returns the entry listed in go.sum as "path version/go.mod".)
   385  func GoModSum(path, version string) (string, error) {
   386  	if !semver.IsValid(version) {
   387  		return "", fmt.Errorf("invalid version %q", version)
   388  	}
   389  	data, err := GoMod(path, version)
   390  	if err != nil {
   391  		return "", err
   392  	}
   393  	sum, err := goModSum(data)
   394  	if err != nil {
   395  		return "", err
   396  	}
   397  	return sum, nil
   398  }
   399  
   400  var errNotCached = fmt.Errorf("not in cache")
   401  
   402  // readDiskStat reads a cached stat result from disk,
   403  // returning the name of the cache file and the result.
   404  // If the read fails, the caller can use
   405  // writeDiskStat(file, info) to write a new cache entry.
   406  func readDiskStat(path, rev string) (file string, info *RevInfo, err error) {
   407  	file, data, err := readDiskCache(path, rev, "info")
   408  	if err != nil {
   409  		// If the cache already contains a pseudo-version with the given hash, we
   410  		// would previously return that pseudo-version without checking upstream.
   411  		// However, that produced an unfortunate side-effect: if the author added a
   412  		// tag to the repository, 'go get' would not pick up the effect of that new
   413  		// tag on the existing commits, and 'go' commands that referred to those
   414  		// commits would use the previous name instead of the new one.
   415  		//
   416  		// That's especially problematic if the original pseudo-version starts with
   417  		// v0.0.0-, as was the case for all pseudo-versions during vgo development,
   418  		// since a v0.0.0- pseudo-version has lower precedence than pretty much any
   419  		// tagged version.
   420  		//
   421  		// In practice, we're only looking up by hash during initial conversion of a
   422  		// legacy config and during an explicit 'go get', and a little extra latency
   423  		// for those operations seems worth the benefit of picking up more accurate
   424  		// versions.
   425  		//
   426  		// Fall back to this resolution scheme only if the GOPROXY setting prohibits
   427  		// us from resolving upstream tags.
   428  		if cfg.GOPROXY == "off" {
   429  			if file, info, err := readDiskStatByHash(path, rev); err == nil {
   430  				return file, info, nil
   431  			}
   432  		}
   433  		return file, nil, err
   434  	}
   435  	info = new(RevInfo)
   436  	if err := json.Unmarshal(data, info); err != nil {
   437  		return file, nil, errNotCached
   438  	}
   439  	// The disk might have stale .info files that have Name and Short fields set.
   440  	// We want to canonicalize to .info files with those fields omitted.
   441  	// Remarshal and update the cache file if needed.
   442  	data2, err := json.Marshal(info)
   443  	if err == nil && !bytes.Equal(data2, data) {
   444  		writeDiskCache(file, data)
   445  	}
   446  	return file, info, nil
   447  }
   448  
   449  // readDiskStatByHash is a fallback for readDiskStat for the case
   450  // where rev is a commit hash instead of a proper semantic version.
   451  // In that case, we look for a cached pseudo-version that matches
   452  // the commit hash. If we find one, we use it.
   453  // This matters most for converting legacy package management
   454  // configs, when we are often looking up commits by full hash.
   455  // Without this check we'd be doing network I/O to the remote repo
   456  // just to find out about a commit we already know about
   457  // (and have cached under its pseudo-version).
   458  func readDiskStatByHash(path, rev string) (file string, info *RevInfo, err error) {
   459  	if PkgMod == "" {
   460  		// Do not download to current directory.
   461  		return "", nil, errNotCached
   462  	}
   463  
   464  	if !codehost.AllHex(rev) || len(rev) < 12 {
   465  		return "", nil, errNotCached
   466  	}
   467  	rev = rev[:12]
   468  	cdir, err := cacheDir(path)
   469  	if err != nil {
   470  		return "", nil, errNotCached
   471  	}
   472  	dir, err := os.Open(cdir)
   473  	if err != nil {
   474  		return "", nil, errNotCached
   475  	}
   476  	names, err := dir.Readdirnames(-1)
   477  	dir.Close()
   478  	if err != nil {
   479  		return "", nil, errNotCached
   480  	}
   481  
   482  	// A given commit hash may map to more than one pseudo-version,
   483  	// depending on which tags are present on the repository.
   484  	// Take the highest such version.
   485  	var maxVersion string
   486  	suffix := "-" + rev + ".info"
   487  	err = errNotCached
   488  	for _, name := range names {
   489  		if strings.HasSuffix(name, suffix) {
   490  			v := strings.TrimSuffix(name, ".info")
   491  			if IsPseudoVersion(v) && semver.Max(maxVersion, v) == v {
   492  				maxVersion = v
   493  				file, info, err = readDiskStat(path, strings.TrimSuffix(name, ".info"))
   494  			}
   495  		}
   496  	}
   497  	return file, info, err
   498  }
   499  
   500  // oldVgoPrefix is the prefix in the old auto-generated cached go.mod files.
   501  // We stopped trying to auto-generate the go.mod files. Now we use a trivial
   502  // go.mod with only a module line, and we've dropped the version prefix
   503  // entirely. If we see a version prefix, that means we're looking at an old copy
   504  // and should ignore it.
   505  var oldVgoPrefix = []byte("//vgo 0.0.")
   506  
   507  // readDiskGoMod reads a cached go.mod file from disk,
   508  // returning the name of the cache file and the result.
   509  // If the read fails, the caller can use
   510  // writeDiskGoMod(file, data) to write a new cache entry.
   511  func readDiskGoMod(path, rev string) (file string, data []byte, err error) {
   512  	file, data, err = readDiskCache(path, rev, "mod")
   513  
   514  	// If the file has an old auto-conversion prefix, pretend it's not there.
   515  	if bytes.HasPrefix(data, oldVgoPrefix) {
   516  		err = errNotCached
   517  		data = nil
   518  	}
   519  
   520  	if err == nil {
   521  		if err := checkGoMod(path, rev, data); err != nil {
   522  			return "", nil, err
   523  		}
   524  	}
   525  
   526  	return file, data, err
   527  }
   528  
   529  // readDiskCache is the generic "read from a cache file" implementation.
   530  // It takes the revision and an identifying suffix for the kind of data being cached.
   531  // It returns the name of the cache file and the content of the file.
   532  // If the read fails, the caller can use
   533  // writeDiskCache(file, data) to write a new cache entry.
   534  func readDiskCache(path, rev, suffix string) (file string, data []byte, err error) {
   535  	file, err = CachePath(module.Version{Path: path, Version: rev}, suffix)
   536  	if err != nil {
   537  		return "", nil, errNotCached
   538  	}
   539  	data, err = renameio.ReadFile(file)
   540  	if err != nil {
   541  		return file, nil, errNotCached
   542  	}
   543  	return file, data, nil
   544  }
   545  
   546  // writeDiskStat writes a stat result cache entry.
   547  // The file name must have been returned by a previous call to readDiskStat.
   548  func writeDiskStat(file string, info *RevInfo) error {
   549  	if file == "" {
   550  		return nil
   551  	}
   552  	js, err := json.Marshal(info)
   553  	if err != nil {
   554  		return err
   555  	}
   556  	return writeDiskCache(file, js)
   557  }
   558  
   559  // writeDiskGoMod writes a go.mod cache entry.
   560  // The file name must have been returned by a previous call to readDiskGoMod.
   561  func writeDiskGoMod(file string, text []byte) error {
   562  	return writeDiskCache(file, text)
   563  }
   564  
   565  // writeDiskCache is the generic "write to a cache file" implementation.
   566  // The file must have been returned by a previous call to readDiskCache.
   567  func writeDiskCache(file string, data []byte) error {
   568  	if file == "" {
   569  		return nil
   570  	}
   571  	// Make sure directory for file exists.
   572  	if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil {
   573  		return err
   574  	}
   575  
   576  	if err := renameio.WriteFile(file, data, 0666); err != nil {
   577  		return err
   578  	}
   579  
   580  	if strings.HasSuffix(file, ".mod") {
   581  		rewriteVersionList(filepath.Dir(file))
   582  	}
   583  	return nil
   584  }
   585  
   586  // rewriteVersionList rewrites the version list in dir
   587  // after a new *.mod file has been written.
   588  func rewriteVersionList(dir string) {
   589  	if filepath.Base(dir) != "@v" {
   590  		base.Fatalf("go: internal error: misuse of rewriteVersionList")
   591  	}
   592  
   593  	listFile := filepath.Join(dir, "list")
   594  
   595  	// We use a separate lockfile here instead of locking listFile itself because
   596  	// we want to use Rename to write the file atomically. The list may be read by
   597  	// a GOPROXY HTTP server, and if we crash midway through a rewrite (or if the
   598  	// HTTP server ignores our locking and serves the file midway through a
   599  	// rewrite) it's better to serve a stale list than a truncated one.
   600  	unlock, err := lockedfile.MutexAt(listFile + ".lock").Lock()
   601  	if err != nil {
   602  		base.Fatalf("go: can't lock version list lockfile: %v", err)
   603  	}
   604  	defer unlock()
   605  
   606  	infos, err := ioutil.ReadDir(dir)
   607  	if err != nil {
   608  		return
   609  	}
   610  	var list []string
   611  	for _, info := range infos {
   612  		// We look for *.mod files on the theory that if we can't supply
   613  		// the .mod file then there's no point in listing that version,
   614  		// since it's unusable. (We can have *.info without *.mod.)
   615  		// We don't require *.zip files on the theory that for code only
   616  		// involved in module graph construction, many *.zip files
   617  		// will never be requested.
   618  		name := info.Name()
   619  		if strings.HasSuffix(name, ".mod") {
   620  			v := strings.TrimSuffix(name, ".mod")
   621  			if v != "" && module.CanonicalVersion(v) == v {
   622  				list = append(list, v)
   623  			}
   624  		}
   625  	}
   626  	SortVersions(list)
   627  
   628  	var buf bytes.Buffer
   629  	for _, v := range list {
   630  		buf.WriteString(v)
   631  		buf.WriteString("\n")
   632  	}
   633  	old, _ := renameio.ReadFile(listFile)
   634  	if bytes.Equal(buf.Bytes(), old) {
   635  		return
   636  	}
   637  
   638  	if err := renameio.WriteFile(listFile, buf.Bytes(), 0666); err != nil {
   639  		base.Fatalf("go: failed to write version list: %v", err)
   640  	}
   641  }