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 = © 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 = © 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 }