| // 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 vgo |
| |
| import ( |
| "archive/zip" |
| "bytes" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| |
| "cmd/go/internal/base" |
| "cmd/go/internal/dirhash" |
| "cmd/go/internal/modfetch" |
| "cmd/go/internal/module" |
| "cmd/go/internal/semver" |
| ) |
| |
| // fetch returns the directory in the local download cache |
| // holding the root of mod's source tree. |
| // It downloads the module if needed. |
| func fetch(mod module.Version) (dir string, err error) { |
| if r := Replacement(mod); r.Path != "" { |
| if r.Version == "" { |
| dir = r.Path |
| if !filepath.IsAbs(dir) { |
| dir = filepath.Join(ModRoot, dir) |
| } |
| return dir, nil |
| } |
| mod = r |
| } |
| |
| modpath := mod.Path + "@" + mod.Version |
| dir = filepath.Join(SrcMod, modpath) |
| if files, _ := ioutil.ReadDir(dir); len(files) == 0 { |
| zipfile := filepath.Join(SrcMod, "cache/download", mod.Path, "@v", mod.Version+".zip") |
| if _, err := os.Stat(zipfile); err == nil { |
| // Use it. |
| // This should only happen if the mod/cache directory is preinitialized |
| // or if src/mod/path was removed but not src/mod/cache/download. |
| fmt.Fprintf(os.Stderr, "vgo: extracting %s %s\n", mod.Path, mod.Version) |
| } else { |
| if err := os.MkdirAll(filepath.Join(SrcMod, "cache/download", mod.Path, "@v"), 0777); err != nil { |
| return "", err |
| } |
| fmt.Fprintf(os.Stderr, "vgo: downloading %s %s\n", mod.Path, mod.Version) |
| if err := downloadZip(mod, zipfile); err != nil { |
| return "", err |
| } |
| } |
| if err := modfetch.Unzip(dir, zipfile, modpath, 0); err != nil { |
| fmt.Fprintf(os.Stderr, "-> %s\n", err) |
| return "", err |
| } |
| } |
| checkModHash(mod) |
| return dir, nil |
| } |
| |
| func downloadZip(mod module.Version, target string) error { |
| repo, err := modfetch.Lookup(mod.Path) |
| if err != nil { |
| return err |
| } |
| tmpfile, err := repo.Zip(mod.Version, os.TempDir()) |
| if err != nil { |
| return err |
| } |
| defer os.Remove(tmpfile) |
| |
| // Double-check zip file looks OK. |
| z, err := zip.OpenReader(tmpfile) |
| if err != nil { |
| z.Close() |
| return err |
| } |
| prefix := mod.Path + "@" + mod.Version |
| for _, f := range z.File { |
| if !strings.HasPrefix(f.Name, prefix) { |
| z.Close() |
| return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name) |
| } |
| } |
| z.Close() |
| |
| hash, err := dirhash.HashZip(tmpfile, dirhash.DefaultHash) |
| if err != nil { |
| return err |
| } |
| r, err := os.Open(tmpfile) |
| if err != nil { |
| return err |
| } |
| defer r.Close() |
| w, err := os.Create(target) |
| if err != nil { |
| return err |
| } |
| if _, err := io.Copy(w, r); err != nil { |
| w.Close() |
| return fmt.Errorf("copying: %v", err) |
| } |
| if err := w.Close(); err != nil { |
| return err |
| } |
| return ioutil.WriteFile(target+"hash", []byte(hash), 0666) |
| } |
| |
| var useModHash = false |
| var modHash map[module.Version][]string |
| |
| func initModHash() { |
| if modHash != nil { |
| return |
| } |
| modHash = make(map[module.Version][]string) |
| file := filepath.Join(ModRoot, "go.modverify") |
| data, err := ioutil.ReadFile(file) |
| if err != nil && os.IsNotExist(err) { |
| return |
| } |
| if err != nil { |
| base.Fatalf("vgo: %v", err) |
| } |
| useModHash = true |
| lineno := 0 |
| for len(data) > 0 { |
| var line []byte |
| lineno++ |
| i := bytes.IndexByte(data, '\n') |
| if i < 0 { |
| line, data = data, nil |
| } else { |
| line, data = data[:i], data[i+1:] |
| } |
| f := strings.Fields(string(line)) |
| if len(f) == 0 { |
| // blank line; skip it |
| continue |
| } |
| if len(f) != 3 { |
| base.Fatalf("vgo: malformed go.modverify:\n%s:%d: wrong number of fields %v", file, lineno, len(f)) |
| } |
| mod := module.Version{Path: f[0], Version: f[1]} |
| modHash[mod] = append(modHash[mod], f[2]) |
| } |
| } |
| |
| func checkModHash(mod module.Version) { |
| initModHash() |
| if !useModHash { |
| return |
| } |
| |
| data, err := ioutil.ReadFile(filepath.Join(SrcMod, "cache/download", mod.Path, "@v", mod.Version+".ziphash")) |
| if err != nil { |
| base.Fatalf("vgo: verifying %s %s: %v", mod.Path, mod.Version, err) |
| } |
| h := strings.TrimSpace(string(data)) |
| if !strings.HasPrefix(h, "h1:") { |
| base.Fatalf("vgo: verifying %s %s: unexpected ziphash: %q", mod.Path, mod.Version, h) |
| } |
| |
| for _, vh := range modHash[mod] { |
| if h == vh { |
| return |
| } |
| if strings.HasPrefix(vh, "h1:") { |
| base.Fatalf("vgo: verifying %s %s: module hash mismatch\n\tdownloaded: %v\n\tgo.modverify: %v", mod.Path, mod.Version, h, vh) |
| } |
| } |
| if len(modHash[mod]) > 0 { |
| fmt.Fprintf(os.Stderr, "warning: verifying %s %s: unknown hashes in go.modverify: %v; adding %v", mod.Path, mod.Version, strings.Join(modHash[mod], ", "), h) |
| } |
| modHash[mod] = append(modHash[mod], h) |
| } |
| |
| func findModHash(mod module.Version) string { |
| data, err := ioutil.ReadFile(filepath.Join(SrcMod, "cache/download", mod.Path, "@v", mod.Version+".ziphash")) |
| if err != nil { |
| return "" |
| } |
| return strings.TrimSpace(string(data)) |
| } |
| |
| func writeModHash() { |
| if !useModHash { |
| return |
| } |
| |
| var mods []module.Version |
| for m := range modHash { |
| mods = append(mods, m) |
| } |
| sortModules(mods) |
| var buf bytes.Buffer |
| for _, m := range mods { |
| list := modHash[m] |
| sort.Strings(list) |
| for _, h := range list { |
| fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) |
| } |
| } |
| |
| file := filepath.Join(ModRoot, "go.modverify") |
| data, _ := ioutil.ReadFile(filepath.Join(ModRoot, "go.modverify")) |
| if bytes.Equal(data, buf.Bytes()) { |
| return |
| } |
| |
| if err := ioutil.WriteFile(file, buf.Bytes(), 0666); err != nil { |
| base.Fatalf("vgo: writing go.modverify: %v", err) |
| } |
| } |
| |
| func sortModules(mods []module.Version) { |
| sort.Slice(mods, func(i, j int) bool { |
| mi := mods[i] |
| mj := mods[j] |
| if mi.Path != mj.Path { |
| return mi.Path < mj.Path |
| } |
| return semver.Compare(mi.Version, mj.Version) < 0 |
| }) |
| } |