github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/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/fs"
    14  	"math/rand"
    15  	"os"
    16  	"path/filepath"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  
    21  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/base"
    22  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/cfg"
    23  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/lockedfile"
    24  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/modfetch/codehost"
    25  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/par"
    26  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/robustio"
    27  
    28  	"github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/module"
    29  	"github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/semver"
    30  )
    31  
    32  func cacheDir(path string) (string, error) {
    33  	if err := checkCacheDir(); err != nil {
    34  		return "", err
    35  	}
    36  	enc, err := module.EscapePath(path)
    37  	if err != nil {
    38  		return "", err
    39  	}
    40  	return filepath.Join(cfg.GOMODCACHE, "cache/download", enc, "/@v"), nil
    41  }
    42  
    43  func CachePath(m module.Version, suffix string) (string, error) {
    44  	dir, err := cacheDir(m.Path)
    45  	if err != nil {
    46  		return "", err
    47  	}
    48  	if !semver.IsValid(m.Version) {
    49  		return "", fmt.Errorf("non-semver module version %q", m.Version)
    50  	}
    51  	if module.CanonicalVersion(m.Version) != m.Version {
    52  		return "", fmt.Errorf("non-canonical module version %q", m.Version)
    53  	}
    54  	encVer, err := module.EscapeVersion(m.Version)
    55  	if err != nil {
    56  		return "", err
    57  	}
    58  	return filepath.Join(dir, encVer+"."+suffix), nil
    59  }
    60  
    61  // DownloadDir returns the directory to which m should have been downloaded.
    62  // An error will be returned if the module path or version cannot be escaped.
    63  // An error satisfying errors.Is(err, fs.ErrNotExist) will be returned
    64  // along with the directory if the directory does not exist or if the directory
    65  // is not completely populated.
    66  func DownloadDir(m module.Version) (string, error) {
    67  	if err := checkCacheDir(); err != nil {
    68  		return "", err
    69  	}
    70  	enc, err := module.EscapePath(m.Path)
    71  	if err != nil {
    72  		return "", err
    73  	}
    74  	if !semver.IsValid(m.Version) {
    75  		return "", fmt.Errorf("non-semver module version %q", m.Version)
    76  	}
    77  	if module.CanonicalVersion(m.Version) != m.Version {
    78  		return "", fmt.Errorf("non-canonical module version %q", m.Version)
    79  	}
    80  	encVer, err := module.EscapeVersion(m.Version)
    81  	if err != nil {
    82  		return "", err
    83  	}
    84  
    85  	// Check whether the directory itself exists.
    86  	dir := filepath.Join(cfg.GOMODCACHE, enc+"@"+encVer)
    87  	if fi, err := os.Stat(dir); os.IsNotExist(err) {
    88  		return dir, err
    89  	} else if err != nil {
    90  		return dir, &DownloadDirPartialError{dir, err}
    91  	} else if !fi.IsDir() {
    92  		return dir, &DownloadDirPartialError{dir, errors.New("not a directory")}
    93  	}
    94  
    95  	// Check if a .partial file exists. This is created at the beginning of
    96  	// a download and removed after the zip is extracted.
    97  	partialPath, err := CachePath(m, "partial")
    98  	if err != nil {
    99  		return dir, err
   100  	}
   101  	if _, err := os.Stat(partialPath); err == nil {
   102  		return dir, &DownloadDirPartialError{dir, errors.New("not completely extracted")}
   103  	} else if !os.IsNotExist(err) {
   104  		return dir, err
   105  	}
   106  
   107  	// Check if a .ziphash file exists. It should be created before the
   108  	// zip is extracted, but if it was deleted (by another program?), we need
   109  	// to re-calculate it. Note that checkMod will repopulate the ziphash
   110  	// file if it doesn't exist, but if the module is excluded by checks
   111  	// through GONOSUMDB or GOPRIVATE, that check and repopulation won't happen.
   112  	ziphashPath, err := CachePath(m, "ziphash")
   113  	if err != nil {
   114  		return dir, err
   115  	}
   116  	if _, err := os.Stat(ziphashPath); os.IsNotExist(err) {
   117  		return dir, &DownloadDirPartialError{dir, errors.New("ziphash file is missing")}
   118  	} else if err != nil {
   119  		return dir, err
   120  	}
   121  	return dir, nil
   122  }
   123  
   124  // DownloadDirPartialError is returned by DownloadDir if a module directory
   125  // exists but was not completely populated.
   126  //
   127  // DownloadDirPartialError is equivalent to fs.ErrNotExist.
   128  type DownloadDirPartialError struct {
   129  	Dir string
   130  	Err error
   131  }
   132  
   133  func (e *DownloadDirPartialError) Error() string     { return fmt.Sprintf("%s: %v", e.Dir, e.Err) }
   134  func (e *DownloadDirPartialError) Is(err error) bool { return err == fs.ErrNotExist }
   135  
   136  // lockVersion locks a file within the module cache that guards the downloading
   137  // and extraction of the zipfile for the given module version.
   138  func lockVersion(mod module.Version) (unlock func(), err error) {
   139  	path, err := CachePath(mod, "lock")
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
   144  		return nil, err
   145  	}
   146  	return lockedfile.MutexAt(path).Lock()
   147  }
   148  
   149  // SideLock locks a file within the module cache that previously guarded
   150  // edits to files outside the cache, such as go.sum and go.mod files in the
   151  // user's working directory.
   152  // If err is nil, the caller MUST eventually call the unlock function.
   153  func SideLock() (unlock func(), err error) {
   154  	if err := checkCacheDir(); err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	path := filepath.Join(cfg.GOMODCACHE, "cache", "lock")
   159  	if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
   160  		return nil, fmt.Errorf("failed to create cache directory: %w", err)
   161  	}
   162  
   163  	return lockedfile.MutexAt(path).Lock()
   164  }
   165  
   166  // A cachingRepo is a cache around an underlying Repo,
   167  // avoiding redundant calls to ModulePath, Versions, Stat, Latest, and GoMod (but not CheckReuse or Zip).
   168  // It is also safe for simultaneous use by multiple goroutines
   169  // (so that it can be returned from Lookup multiple times).
   170  // It serializes calls to the underlying Repo.
   171  type cachingRepo struct {
   172  	path  string
   173  	cache par.Cache // cache for all operations
   174  
   175  	once     sync.Once
   176  	initRepo func() (Repo, error)
   177  	r        Repo
   178  }
   179  
   180  func newCachingRepo(path string, initRepo func() (Repo, error)) *cachingRepo {
   181  	return &cachingRepo{
   182  		path:     path,
   183  		initRepo: initRepo,
   184  	}
   185  }
   186  
   187  func (r *cachingRepo) repo() Repo {
   188  	r.once.Do(func() {
   189  		var err error
   190  		r.r, err = r.initRepo()
   191  		if err != nil {
   192  			r.r = errRepo{r.path, err}
   193  		}
   194  	})
   195  	return r.r
   196  }
   197  
   198  func (r *cachingRepo) CheckReuse(old *codehost.Origin) error {
   199  	return r.repo().CheckReuse(old)
   200  }
   201  
   202  func (r *cachingRepo) ModulePath() string {
   203  	return r.path
   204  }
   205  
   206  func (r *cachingRepo) Versions(prefix string) (*Versions, error) {
   207  	type cached struct {
   208  		v   *Versions
   209  		err error
   210  	}
   211  	c := r.cache.Do("versions:"+prefix, func() any {
   212  		v, err := r.repo().Versions(prefix)
   213  		return cached{v, err}
   214  	}).(cached)
   215  
   216  	if c.err != nil {
   217  		return nil, c.err
   218  	}
   219  	v := &Versions{
   220  		Origin: c.v.Origin,
   221  		List:   append([]string(nil), c.v.List...),
   222  	}
   223  	return v, nil
   224  }
   225  
   226  type cachedInfo struct {
   227  	info *RevInfo
   228  	err  error
   229  }
   230  
   231  func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
   232  	c := r.cache.Do("stat:"+rev, func() any {
   233  		file, info, err := readDiskStat(r.path, rev)
   234  		if err == nil {
   235  			return cachedInfo{info, nil}
   236  		}
   237  
   238  		info, err = r.repo().Stat(rev)
   239  		if err == nil {
   240  			// If we resolved, say, 1234abcde to v0.0.0-20180604122334-1234abcdef78,
   241  			// then save the information under the proper version, for future use.
   242  			if info.Version != rev {
   243  				file, _ = CachePath(module.Version{Path: r.path, Version: info.Version}, "info")
   244  				r.cache.Do("stat:"+info.Version, func() any {
   245  					return cachedInfo{info, err}
   246  				})
   247  			}
   248  
   249  			if err := writeDiskStat(file, info); err != nil {
   250  				fmt.Fprintf(os.Stderr, "go: writing stat cache: %v\n", err)
   251  			}
   252  		}
   253  		return cachedInfo{info, err}
   254  	}).(cachedInfo)
   255  
   256  	info := c.info
   257  	if info != nil {
   258  		copy := *info
   259  		info = &copy
   260  	}
   261  	return info, c.err
   262  }
   263  
   264  func (r *cachingRepo) Latest() (*RevInfo, error) {
   265  	c := r.cache.Do("latest:", func() any {
   266  		info, err := r.repo().Latest()
   267  
   268  		// Save info for likely future Stat call.
   269  		if err == nil {
   270  			r.cache.Do("stat:"+info.Version, func() any {
   271  				return cachedInfo{info, err}
   272  			})
   273  			if file, _, err := readDiskStat(r.path, info.Version); err != nil {
   274  				writeDiskStat(file, info)
   275  			}
   276  		}
   277  
   278  		return cachedInfo{info, err}
   279  	}).(cachedInfo)
   280  
   281  	info := c.info
   282  	if info != nil {
   283  		copy := *info
   284  		info = &copy
   285  	}
   286  	return info, c.err
   287  }
   288  
   289  func (r *cachingRepo) GoMod(version string) ([]byte, error) {
   290  	type cached struct {
   291  		text []byte
   292  		err  error
   293  	}
   294  	c := r.cache.Do("gomod:"+version, func() any {
   295  		file, text, err := readDiskGoMod(r.path, version)
   296  		if err == nil {
   297  			// Note: readDiskGoMod already called checkGoMod.
   298  			return cached{text, nil}
   299  		}
   300  
   301  		text, err = r.repo().GoMod(version)
   302  		if err == nil {
   303  			if err := checkGoMod(r.path, version, text); err != nil {
   304  				return cached{text, err}
   305  			}
   306  			if err := writeDiskGoMod(file, text); err != nil {
   307  				fmt.Fprintf(os.Stderr, "go: writing go.mod cache: %v\n", err)
   308  			}
   309  		}
   310  		return cached{text, err}
   311  	}).(cached)
   312  
   313  	if c.err != nil {
   314  		return nil, c.err
   315  	}
   316  	return append([]byte(nil), c.text...), nil
   317  }
   318  
   319  func (r *cachingRepo) Zip(dst io.Writer, version string) error {
   320  	return r.repo().Zip(dst, version)
   321  }
   322  
   323  // InfoFile is like Lookup(path).Stat(version) but also returns the name of the file
   324  // containing the cached information.
   325  func InfoFile(path, version string) (*RevInfo, string, error) {
   326  	if !semver.IsValid(version) {
   327  		return nil, "", fmt.Errorf("invalid version %q", version)
   328  	}
   329  
   330  	if file, info, err := readDiskStat(path, version); err == nil {
   331  		return info, file, nil
   332  	}
   333  
   334  	var info *RevInfo
   335  	var err2info map[error]*RevInfo
   336  	err := TryProxies(func(proxy string) error {
   337  		i, err := Lookup(proxy, path).Stat(version)
   338  		if err == nil {
   339  			info = i
   340  		} else {
   341  			if err2info == nil {
   342  				err2info = make(map[error]*RevInfo)
   343  			}
   344  			err2info[err] = info
   345  		}
   346  		return err
   347  	})
   348  	if err != nil {
   349  		return err2info[err], "", err
   350  	}
   351  
   352  	// Stat should have populated the disk cache for us.
   353  	file, err := CachePath(module.Version{Path: path, Version: version}, "info")
   354  	if err != nil {
   355  		return nil, "", err
   356  	}
   357  	return info, file, nil
   358  }
   359  
   360  // GoMod is like Lookup(path).GoMod(rev) but avoids the
   361  // repository path resolution in Lookup if the result is
   362  // already cached on local disk.
   363  func GoMod(path, rev string) ([]byte, error) {
   364  	// Convert commit hash to pseudo-version
   365  	// to increase cache hit rate.
   366  	if !semver.IsValid(rev) {
   367  		if _, info, err := readDiskStat(path, rev); err == nil {
   368  			rev = info.Version
   369  		} else {
   370  			if errors.Is(err, statCacheErr) {
   371  				return nil, err
   372  			}
   373  			err := TryProxies(func(proxy string) error {
   374  				info, err := Lookup(proxy, path).Stat(rev)
   375  				if err == nil {
   376  					rev = info.Version
   377  				}
   378  				return err
   379  			})
   380  			if err != nil {
   381  				return nil, err
   382  			}
   383  		}
   384  	}
   385  
   386  	_, data, err := readDiskGoMod(path, rev)
   387  	if err == nil {
   388  		return data, nil
   389  	}
   390  
   391  	err = TryProxies(func(proxy string) (err error) {
   392  		data, err = Lookup(proxy, path).GoMod(rev)
   393  		return err
   394  	})
   395  	return data, err
   396  }
   397  
   398  // GoModFile is like GoMod but returns the name of the file containing
   399  // the cached information.
   400  func GoModFile(path, version string) (string, error) {
   401  	if !semver.IsValid(version) {
   402  		return "", fmt.Errorf("invalid version %q", version)
   403  	}
   404  	if _, err := GoMod(path, version); err != nil {
   405  		return "", err
   406  	}
   407  	// GoMod should have populated the disk cache for us.
   408  	file, err := CachePath(module.Version{Path: path, Version: version}, "mod")
   409  	if err != nil {
   410  		return "", err
   411  	}
   412  	return file, nil
   413  }
   414  
   415  // GoModSum returns the go.sum entry for the module version's go.mod file.
   416  // (That is, it returns the entry listed in go.sum as "path version/go.mod".)
   417  func GoModSum(path, version string) (string, error) {
   418  	if !semver.IsValid(version) {
   419  		return "", fmt.Errorf("invalid version %q", version)
   420  	}
   421  	data, err := GoMod(path, version)
   422  	if err != nil {
   423  		return "", err
   424  	}
   425  	sum, err := goModSum(data)
   426  	if err != nil {
   427  		return "", err
   428  	}
   429  	return sum, nil
   430  }
   431  
   432  var errNotCached = fmt.Errorf("not in cache")
   433  
   434  // readDiskStat reads a cached stat result from disk,
   435  // returning the name of the cache file and the result.
   436  // If the read fails, the caller can use
   437  // writeDiskStat(file, info) to write a new cache entry.
   438  func readDiskStat(path, rev string) (file string, info *RevInfo, err error) {
   439  	file, data, err := readDiskCache(path, rev, "info")
   440  	if err != nil {
   441  		// If the cache already contains a pseudo-version with the given hash, we
   442  		// would previously return that pseudo-version without checking upstream.
   443  		// However, that produced an unfortunate side-effect: if the author added a
   444  		// tag to the repository, 'go get' would not pick up the effect of that new
   445  		// tag on the existing commits, and 'go' commands that referred to those
   446  		// commits would use the previous name instead of the new one.
   447  		//
   448  		// That's especially problematic if the original pseudo-version starts with
   449  		// v0.0.0-, as was the case for all pseudo-versions during vgo development,
   450  		// since a v0.0.0- pseudo-version has lower precedence than pretty much any
   451  		// tagged version.
   452  		//
   453  		// In practice, we're only looking up by hash during initial conversion of a
   454  		// legacy config and during an explicit 'go get', and a little extra latency
   455  		// for those operations seems worth the benefit of picking up more accurate
   456  		// versions.
   457  		//
   458  		// Fall back to this resolution scheme only if the GOPROXY setting prohibits
   459  		// us from resolving upstream tags.
   460  		if cfg.GOPROXY == "off" {
   461  			if file, info, err := readDiskStatByHash(path, rev); err == nil {
   462  				return file, info, nil
   463  			}
   464  		}
   465  		return file, nil, err
   466  	}
   467  	info = new(RevInfo)
   468  	if err := json.Unmarshal(data, info); err != nil {
   469  		return file, nil, errNotCached
   470  	}
   471  	// The disk might have stale .info files that have Name and Short fields set.
   472  	// We want to canonicalize to .info files with those fields omitted.
   473  	// Remarshal and update the cache file if needed.
   474  	data2, err := json.Marshal(info)
   475  	if err == nil && !bytes.Equal(data2, data) {
   476  		writeDiskCache(file, data)
   477  	}
   478  	return file, info, nil
   479  }
   480  
   481  // readDiskStatByHash is a fallback for readDiskStat for the case
   482  // where rev is a commit hash instead of a proper semantic version.
   483  // In that case, we look for a cached pseudo-version that matches
   484  // the commit hash. If we find one, we use it.
   485  // This matters most for converting legacy package management
   486  // configs, when we are often looking up commits by full hash.
   487  // Without this check we'd be doing network I/O to the remote repo
   488  // just to find out about a commit we already know about
   489  // (and have cached under its pseudo-version).
   490  func readDiskStatByHash(path, rev string) (file string, info *RevInfo, err error) {
   491  	if cfg.GOMODCACHE == "" {
   492  		// Do not download to current directory.
   493  		return "", nil, errNotCached
   494  	}
   495  
   496  	if !codehost.AllHex(rev) || len(rev) < 12 {
   497  		return "", nil, errNotCached
   498  	}
   499  	rev = rev[:12]
   500  	cdir, err := cacheDir(path)
   501  	if err != nil {
   502  		return "", nil, errNotCached
   503  	}
   504  	dir, err := os.Open(cdir)
   505  	if err != nil {
   506  		return "", nil, errNotCached
   507  	}
   508  	names, err := dir.Readdirnames(-1)
   509  	dir.Close()
   510  	if err != nil {
   511  		return "", nil, errNotCached
   512  	}
   513  
   514  	// A given commit hash may map to more than one pseudo-version,
   515  	// depending on which tags are present on the repository.
   516  	// Take the highest such version.
   517  	var maxVersion string
   518  	suffix := "-" + rev + ".info"
   519  	err = errNotCached
   520  	for _, name := range names {
   521  		if strings.HasSuffix(name, suffix) {
   522  			v := strings.TrimSuffix(name, ".info")
   523  			if module.IsPseudoVersion(v) && semver.Compare(v, maxVersion) > 0 {
   524  				maxVersion = v
   525  				file, info, err = readDiskStat(path, strings.TrimSuffix(name, ".info"))
   526  			}
   527  		}
   528  	}
   529  	return file, info, err
   530  }
   531  
   532  // oldVgoPrefix is the prefix in the old auto-generated cached go.mod files.
   533  // We stopped trying to auto-generate the go.mod files. Now we use a trivial
   534  // go.mod with only a module line, and we've dropped the version prefix
   535  // entirely. If we see a version prefix, that means we're looking at an old copy
   536  // and should ignore it.
   537  var oldVgoPrefix = []byte("//vgo 0.0.")
   538  
   539  // readDiskGoMod reads a cached go.mod file from disk,
   540  // returning the name of the cache file and the result.
   541  // If the read fails, the caller can use
   542  // writeDiskGoMod(file, data) to write a new cache entry.
   543  func readDiskGoMod(path, rev string) (file string, data []byte, err error) {
   544  	file, data, err = readDiskCache(path, rev, "mod")
   545  
   546  	// If the file has an old auto-conversion prefix, pretend it's not there.
   547  	if bytes.HasPrefix(data, oldVgoPrefix) {
   548  		err = errNotCached
   549  		data = nil
   550  	}
   551  
   552  	if err == nil {
   553  		if err := checkGoMod(path, rev, data); err != nil {
   554  			return "", nil, err
   555  		}
   556  	}
   557  
   558  	return file, data, err
   559  }
   560  
   561  // readDiskCache is the generic "read from a cache file" implementation.
   562  // It takes the revision and an identifying suffix for the kind of data being cached.
   563  // It returns the name of the cache file and the content of the file.
   564  // If the read fails, the caller can use
   565  // writeDiskCache(file, data) to write a new cache entry.
   566  func readDiskCache(path, rev, suffix string) (file string, data []byte, err error) {
   567  	file, err = CachePath(module.Version{Path: path, Version: rev}, suffix)
   568  	if err != nil {
   569  		return "", nil, errNotCached
   570  	}
   571  	data, err = robustio.ReadFile(file)
   572  	if err != nil {
   573  		return file, nil, errNotCached
   574  	}
   575  	return file, data, nil
   576  }
   577  
   578  // writeDiskStat writes a stat result cache entry.
   579  // The file name must have been returned by a previous call to readDiskStat.
   580  func writeDiskStat(file string, info *RevInfo) error {
   581  	if file == "" {
   582  		return nil
   583  	}
   584  
   585  	if info.Origin != nil {
   586  		// Clean the origin information, which might have too many
   587  		// validation criteria, for example if we are saving the result of
   588  		// m@master as m@pseudo-version.
   589  		clean := *info
   590  		info = &clean
   591  		o := *info.Origin
   592  		info.Origin = &o
   593  
   594  		// Tags never matter if you are starting with a semver version,
   595  		// as we would be when finding this cache entry.
   596  		o.TagSum = ""
   597  		o.TagPrefix = ""
   598  		// Ref doesn't matter if you have a pseudoversion.
   599  		if module.IsPseudoVersion(info.Version) {
   600  			o.Ref = ""
   601  		}
   602  	}
   603  
   604  	js, err := json.Marshal(info)
   605  	if err != nil {
   606  		return err
   607  	}
   608  	return writeDiskCache(file, js)
   609  }
   610  
   611  // writeDiskGoMod writes a go.mod cache entry.
   612  // The file name must have been returned by a previous call to readDiskGoMod.
   613  func writeDiskGoMod(file string, text []byte) error {
   614  	return writeDiskCache(file, text)
   615  }
   616  
   617  // writeDiskCache is the generic "write to a cache file" implementation.
   618  // The file must have been returned by a previous call to readDiskCache.
   619  func writeDiskCache(file string, data []byte) error {
   620  	if file == "" {
   621  		return nil
   622  	}
   623  	// Make sure directory for file exists.
   624  	if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil {
   625  		return err
   626  	}
   627  
   628  	// Write the file to a temporary location, and then rename it to its final
   629  	// path to reduce the likelihood of a corrupt file existing at that final path.
   630  	f, err := tempFile(filepath.Dir(file), filepath.Base(file), 0666)
   631  	if err != nil {
   632  		return err
   633  	}
   634  	defer func() {
   635  		// Only call os.Remove on f.Name() if we failed to rename it: otherwise,
   636  		// some other process may have created a new file with the same name after
   637  		// the rename completed.
   638  		if err != nil {
   639  			f.Close()
   640  			os.Remove(f.Name())
   641  		}
   642  	}()
   643  
   644  	if _, err := f.Write(data); err != nil {
   645  		return err
   646  	}
   647  	if err := f.Close(); err != nil {
   648  		return err
   649  	}
   650  	if err := robustio.Rename(f.Name(), file); err != nil {
   651  		return err
   652  	}
   653  
   654  	if strings.HasSuffix(file, ".mod") {
   655  		rewriteVersionList(filepath.Dir(file))
   656  	}
   657  	return nil
   658  }
   659  
   660  // tempFile creates a new temporary file with given permission bits.
   661  func tempFile(dir, prefix string, perm fs.FileMode) (f *os.File, err error) {
   662  	for i := 0; i < 10000; i++ {
   663  		name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+".tmp")
   664  		f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
   665  		if os.IsExist(err) {
   666  			continue
   667  		}
   668  		break
   669  	}
   670  	return
   671  }
   672  
   673  // rewriteVersionList rewrites the version list in dir
   674  // after a new *.mod file has been written.
   675  func rewriteVersionList(dir string) (err error) {
   676  	if filepath.Base(dir) != "@v" {
   677  		base.Fatalf("go: internal error: misuse of rewriteVersionList")
   678  	}
   679  
   680  	listFile := filepath.Join(dir, "list")
   681  
   682  	// Lock listfile when writing to it to try to avoid corruption to the file.
   683  	// Under rare circumstances, for instance, if the system loses power in the
   684  	// middle of a write it is possible for corrupt data to be written. This is
   685  	// not a problem for the go command itself, but may be an issue if the
   686  	// cache is being served by a GOPROXY HTTP server. This will be corrected
   687  	// the next time a new version of the module is fetched and the file is rewritten.
   688  	// TODO(matloob): golang.org/issue/43313 covers adding a go mod verify
   689  	// command that removes module versions that fail checksums. It should also
   690  	// remove list files that are detected to be corrupt.
   691  	f, err := lockedfile.Edit(listFile)
   692  	if err != nil {
   693  		return err
   694  	}
   695  	defer func() {
   696  		if cerr := f.Close(); cerr != nil && err == nil {
   697  			err = cerr
   698  		}
   699  	}()
   700  	infos, err := os.ReadDir(dir)
   701  	if err != nil {
   702  		return err
   703  	}
   704  	var list []string
   705  	for _, info := range infos {
   706  		// We look for *.mod files on the theory that if we can't supply
   707  		// the .mod file then there's no point in listing that version,
   708  		// since it's unusable. (We can have *.info without *.mod.)
   709  		// We don't require *.zip files on the theory that for code only
   710  		// involved in module graph construction, many *.zip files
   711  		// will never be requested.
   712  		name := info.Name()
   713  		if v, found := strings.CutSuffix(name, ".mod"); found {
   714  			if v != "" && module.CanonicalVersion(v) == v {
   715  				list = append(list, v)
   716  			}
   717  		}
   718  	}
   719  	semver.Sort(list)
   720  
   721  	var buf bytes.Buffer
   722  	for _, v := range list {
   723  		buf.WriteString(v)
   724  		buf.WriteString("\n")
   725  	}
   726  	if fi, err := f.Stat(); err == nil && int(fi.Size()) == buf.Len() {
   727  		old := make([]byte, buf.Len()+1)
   728  		if n, err := f.ReadAt(old, 0); err == io.EOF && n == buf.Len() && bytes.Equal(buf.Bytes(), old) {
   729  			return nil // No edit needed.
   730  		}
   731  	}
   732  	// Remove existing contents, so that when we truncate to the actual size it will zero-fill,
   733  	// and we will be able to detect (some) incomplete writes as files containing trailing NUL bytes.
   734  	if err := f.Truncate(0); err != nil {
   735  		return err
   736  	}
   737  	// Reserve the final size and zero-fill.
   738  	if err := f.Truncate(int64(buf.Len())); err != nil {
   739  		return err
   740  	}
   741  	// Write the actual contents. If this fails partway through,
   742  	// the remainder of the file should remain as zeroes.
   743  	if _, err := f.Write(buf.Bytes()); err != nil {
   744  		f.Truncate(0)
   745  		return err
   746  	}
   747  
   748  	return nil
   749  }
   750  
   751  var (
   752  	statCacheOnce sync.Once
   753  	statCacheErr  error
   754  )
   755  
   756  // checkCacheDir checks if the directory specified by GOMODCACHE exists. An
   757  // error is returned if it does not.
   758  func checkCacheDir() error {
   759  	if cfg.GOMODCACHE == "" {
   760  		// modload.Init exits if GOPATH[0] is empty, and cfg.GOMODCACHE
   761  		// is set to GOPATH[0]/pkg/mod if GOMODCACHE is empty, so this should never happen.
   762  		return fmt.Errorf("module cache not found: neither GOMODCACHE nor GOPATH is set")
   763  	}
   764  	if !filepath.IsAbs(cfg.GOMODCACHE) {
   765  		return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q.\n", cfg.GOMODCACHE)
   766  	}
   767  
   768  	// os.Stat is slow on Windows, so we only call it once to prevent unnecessary
   769  	// I/O every time this function is called.
   770  	statCacheOnce.Do(func() {
   771  		fi, err := os.Stat(cfg.GOMODCACHE)
   772  		if err != nil {
   773  			if !os.IsNotExist(err) {
   774  				statCacheErr = fmt.Errorf("could not create module cache: %w", err)
   775  				return
   776  			}
   777  			if err := os.MkdirAll(cfg.GOMODCACHE, 0777); err != nil {
   778  				statCacheErr = fmt.Errorf("could not create module cache: %w", err)
   779  				return
   780  			}
   781  			return
   782  		}
   783  		if !fi.IsDir() {
   784  			statCacheErr = fmt.Errorf("could not create module cache: %q is not a directory", cfg.GOMODCACHE)
   785  			return
   786  		}
   787  	})
   788  	return statCacheErr
   789  }