// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modfetch
import (
"archive/zip"
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/lockedfile"
"cmd/go/internal/par"
"cmd/go/internal/robustio"
"cmd/go/internal/trace"
"golang.org/x/mod/module"
"golang.org/x/mod/sumdb/dirhash"
modzip "golang.org/x/mod/zip"
)
var downloadCache par.Cache
// Download downloads the specific module version to the
// local download cache and returns the name of the directory
// corresponding to the root of the module's file tree.
func Download(ctx context.Context, mod module.Version) (dir string, err error) {
if err := checkCacheDir(); err != nil {
base.Fatalf("go: %v", err)
}
// The par.Cache here avoids duplicate work.
type cached struct {
dir string
err error
}
c := downloadCache.Do(mod, func() interface{} {
dir, err := download(ctx, mod)
if err != nil {
return cached{"", err}
}
checkMod(mod)
return cached{dir, nil}
}).(cached)
return c.dir, c.err
}
func download(ctx context.Context, mod module.Version) (dir string, err error) {
ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String())
defer span.Done()
dir, err = DownloadDir(mod)
if err == nil {
// The directory has already been completely extracted (no .partial file exists).
return dir, nil
} else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
return "", err
}
// To avoid cluttering the cache with extraneous files,
// DownloadZip uses the same lockfile as Download.
// Invoke DownloadZip before locking the file.
zipfile, err := DownloadZip(ctx, mod)
if err != nil {
return "", err
}
unlock, err := lockVersion(mod)
if err != nil {
return "", err
}
defer unlock()
ctx, span = trace.StartSpan(ctx, "unzip "+zipfile)
defer span.Done()
// Check whether the directory was populated while we were waiting on the lock.
_, dirErr := DownloadDir(mod)
if dirErr == nil {
return dir, nil
}
_, dirExists := dirErr.(*DownloadDirPartialError)
// Clean up any remaining temporary directories created by old versions
// (before 1.16), as well as partially extracted directories (indicated by
// DownloadDirPartialError, usually because of a .partial file). This is only
// safe to do because the lock file ensures that their writers are no longer
// active.
parentDir := filepath.Dir(dir)
tmpPrefix := filepath.Base(dir) + ".tmp-"
if old, err := filepath.Glob(filepath.Join(parentDir, tmpPrefix+"*")); err == nil {
for _, path := range old {
RemoveAll(path) // best effort
}
}
if dirExists {
if err := RemoveAll(dir); err != nil {
return "", err
}
}
partialPath, err := CachePath(mod, "partial")
if err != nil {
return "", err
}
// Extract the module zip directory at its final location.
//
// To prevent other processes from reading the directory if we crash,
// create a .partial file before extracting the directory, and delete
// the .partial file afterward (all while holding the lock).
//
// Before Go 1.16, we extracted to a temporary directory with a random name
// then renamed it into place with os.Rename. On Windows, this failed with
// ERROR_ACCESS_DENIED when another process (usually an anti-virus scanner)
// opened files in the temporary directory.
//
// Go 1.14.2 and higher respect .partial files. Older versions may use
// partially extracted directories. 'go mod verify' can detect this,
// and 'go clean -modcache' can fix it.
if err := os.MkdirAll(parentDir, 0777); err != nil {
return "", err
}
if err := os.WriteFile(partialPath, nil, 0666); err != nil {
return "", err
}
if err := modzip