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 }