github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/go/internal/modfetch/fetch.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 "archive/zip" 9 "bytes" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "sort" 16 "strings" 17 "sync" 18 19 "cmd/go/internal/base" 20 "cmd/go/internal/cfg" 21 "cmd/go/internal/dirhash" 22 "cmd/go/internal/module" 23 "cmd/go/internal/par" 24 ) 25 26 var downloadCache par.Cache 27 28 // Download downloads the specific module version to the 29 // local download cache and returns the name of the directory 30 // corresponding to the root of the module's file tree. 31 func Download(mod module.Version) (dir string, err error) { 32 if PkgMod == "" { 33 // Do not download to current directory. 34 return "", fmt.Errorf("missing modfetch.PkgMod") 35 } 36 37 // The par.Cache here avoids duplicate work but also 38 // avoids conflicts from simultaneous calls by multiple goroutines 39 // for the same version. 40 type cached struct { 41 dir string 42 err error 43 } 44 c := downloadCache.Do(mod, func() interface{} { 45 dir, err := DownloadDir(mod) 46 if err != nil { 47 return cached{"", err} 48 } 49 if files, _ := ioutil.ReadDir(dir); len(files) == 0 { 50 zipfile, err := DownloadZip(mod) 51 if err != nil { 52 return cached{"", err} 53 } 54 modpath := mod.Path + "@" + mod.Version 55 if err := Unzip(dir, zipfile, modpath, 0); err != nil { 56 fmt.Fprintf(os.Stderr, "-> %s\n", err) 57 return cached{"", err} 58 } 59 } 60 checkSum(mod) 61 return cached{dir, nil} 62 }).(cached) 63 return c.dir, c.err 64 } 65 66 var downloadZipCache par.Cache 67 68 // DownloadZip downloads the specific module version to the 69 // local zip cache and returns the name of the zip file. 70 func DownloadZip(mod module.Version) (zipfile string, err error) { 71 // The par.Cache here avoids duplicate work but also 72 // avoids conflicts from simultaneous calls by multiple goroutines 73 // for the same version. 74 type cached struct { 75 zipfile string 76 err error 77 } 78 c := downloadZipCache.Do(mod, func() interface{} { 79 zipfile, err := CachePath(mod, "zip") 80 if err != nil { 81 return cached{"", err} 82 } 83 if _, err := os.Stat(zipfile); err == nil { 84 // Use it. 85 // This should only happen if the mod/cache directory is preinitialized 86 // or if pkg/mod/path was removed but not pkg/mod/cache/download. 87 if cfg.CmdName != "mod download" { 88 fmt.Fprintf(os.Stderr, "go: extracting %s %s\n", mod.Path, mod.Version) 89 } 90 } else { 91 if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil { 92 return cached{"", err} 93 } 94 if cfg.CmdName != "mod download" { 95 fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, mod.Version) 96 } 97 if err := downloadZip(mod, zipfile); err != nil { 98 return cached{"", err} 99 } 100 } 101 return cached{zipfile, nil} 102 }).(cached) 103 return c.zipfile, c.err 104 } 105 106 func downloadZip(mod module.Version, target string) error { 107 repo, err := Lookup(mod.Path) 108 if err != nil { 109 return err 110 } 111 tmpfile, err := repo.Zip(mod.Version, os.TempDir()) 112 if err != nil { 113 return err 114 } 115 defer os.Remove(tmpfile) 116 117 // Double-check zip file looks OK. 118 z, err := zip.OpenReader(tmpfile) 119 if err != nil { 120 return err 121 } 122 prefix := mod.Path + "@" + mod.Version 123 for _, f := range z.File { 124 if !strings.HasPrefix(f.Name, prefix) { 125 z.Close() 126 return fmt.Errorf("zip for %s has unexpected file %s", prefix, f.Name) 127 } 128 } 129 z.Close() 130 131 hash, err := dirhash.HashZip(tmpfile, dirhash.DefaultHash) 132 if err != nil { 133 return err 134 } 135 checkOneSum(mod, hash) // check before installing the zip file 136 r, err := os.Open(tmpfile) 137 if err != nil { 138 return err 139 } 140 defer r.Close() 141 w, err := os.Create(target) 142 if err != nil { 143 return err 144 } 145 if _, err := io.Copy(w, r); err != nil { 146 w.Close() 147 return fmt.Errorf("copying: %v", err) 148 } 149 if err := w.Close(); err != nil { 150 return err 151 } 152 return ioutil.WriteFile(target+"hash", []byte(hash), 0666) 153 } 154 155 var GoSumFile string // path to go.sum; set by package modload 156 157 var goSum struct { 158 mu sync.Mutex 159 m map[module.Version][]string // content of go.sum file (+ go.modverify if present) 160 enabled bool // whether to use go.sum at all 161 modverify string // path to go.modverify, to be deleted 162 } 163 164 // initGoSum initializes the go.sum data. 165 // It reports whether use of go.sum is now enabled. 166 // The goSum lock must be held. 167 func initGoSum() bool { 168 if GoSumFile == "" { 169 return false 170 } 171 if goSum.m != nil { 172 return true 173 } 174 175 goSum.m = make(map[module.Version][]string) 176 data, err := ioutil.ReadFile(GoSumFile) 177 if err != nil && !os.IsNotExist(err) { 178 base.Fatalf("go: %v", err) 179 } 180 goSum.enabled = true 181 readGoSum(GoSumFile, data) 182 183 // Add old go.modverify file. 184 // We'll delete go.modverify in WriteGoSum. 185 alt := strings.TrimSuffix(GoSumFile, ".sum") + ".modverify" 186 if data, err := ioutil.ReadFile(alt); err == nil { 187 readGoSum(alt, data) 188 goSum.modverify = alt 189 } 190 return true 191 } 192 193 // emptyGoModHash is the hash of a 1-file tree containing a 0-length go.mod. 194 // A bug caused us to write these into go.sum files for non-modules. 195 // We detect and remove them. 196 const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY=" 197 198 // readGoSum parses data, which is the content of file, 199 // and adds it to goSum.m. The goSum lock must be held. 200 func readGoSum(file string, data []byte) { 201 lineno := 0 202 for len(data) > 0 { 203 var line []byte 204 lineno++ 205 i := bytes.IndexByte(data, '\n') 206 if i < 0 { 207 line, data = data, nil 208 } else { 209 line, data = data[:i], data[i+1:] 210 } 211 f := strings.Fields(string(line)) 212 if len(f) == 0 { 213 // blank line; skip it 214 continue 215 } 216 if len(f) != 3 { 217 base.Fatalf("go: malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f)) 218 } 219 if f[2] == emptyGoModHash { 220 // Old bug; drop it. 221 continue 222 } 223 mod := module.Version{Path: f[0], Version: f[1]} 224 goSum.m[mod] = append(goSum.m[mod], f[2]) 225 } 226 } 227 228 // checkSum checks the given module's checksum. 229 func checkSum(mod module.Version) { 230 if PkgMod == "" { 231 // Do not use current directory. 232 return 233 } 234 235 // Do the file I/O before acquiring the go.sum lock. 236 ziphash, err := CachePath(mod, "ziphash") 237 if err != nil { 238 base.Fatalf("go: verifying %s@%s: %v", mod.Path, mod.Version, err) 239 } 240 data, err := ioutil.ReadFile(ziphash) 241 if err != nil { 242 if os.IsNotExist(err) { 243 // This can happen if someone does rm -rf GOPATH/src/cache/download. So it goes. 244 return 245 } 246 base.Fatalf("go: verifying %s@%s: %v", mod.Path, mod.Version, err) 247 } 248 h := strings.TrimSpace(string(data)) 249 if !strings.HasPrefix(h, "h1:") { 250 base.Fatalf("go: verifying %s@%s: unexpected ziphash: %q", mod.Path, mod.Version, h) 251 } 252 253 checkOneSum(mod, h) 254 } 255 256 // goModSum returns the checksum for the go.mod contents. 257 func goModSum(data []byte) (string, error) { 258 return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) { 259 return ioutil.NopCloser(bytes.NewReader(data)), nil 260 }) 261 } 262 263 // checkGoMod checks the given module's go.mod checksum; 264 // data is the go.mod content. 265 func checkGoMod(path, version string, data []byte) { 266 h, err := goModSum(data) 267 if err != nil { 268 base.Fatalf("go: verifying %s %s go.mod: %v", path, version, err) 269 } 270 271 checkOneSum(module.Version{Path: path, Version: version + "/go.mod"}, h) 272 } 273 274 // checkOneSum checks that the recorded hash for mod is h. 275 func checkOneSum(mod module.Version, h string) { 276 goSum.mu.Lock() 277 defer goSum.mu.Unlock() 278 if !initGoSum() { 279 return 280 } 281 282 for _, vh := range goSum.m[mod] { 283 if h == vh { 284 return 285 } 286 if strings.HasPrefix(vh, "h1:") { 287 base.Fatalf("go: verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tgo.sum: %v", mod.Path, mod.Version, h, vh) 288 } 289 } 290 if len(goSum.m[mod]) > 0 { 291 fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v", mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h) 292 } 293 goSum.m[mod] = append(goSum.m[mod], h) 294 } 295 296 // Sum returns the checksum for the downloaded copy of the given module, 297 // if present in the download cache. 298 func Sum(mod module.Version) string { 299 if PkgMod == "" { 300 // Do not use current directory. 301 return "" 302 } 303 304 ziphash, err := CachePath(mod, "ziphash") 305 if err != nil { 306 return "" 307 } 308 data, err := ioutil.ReadFile(ziphash) 309 if err != nil { 310 return "" 311 } 312 return strings.TrimSpace(string(data)) 313 } 314 315 // WriteGoSum writes the go.sum file if it needs to be updated. 316 func WriteGoSum() { 317 goSum.mu.Lock() 318 defer goSum.mu.Unlock() 319 if !initGoSum() { 320 return 321 } 322 323 var mods []module.Version 324 for m := range goSum.m { 325 mods = append(mods, m) 326 } 327 module.Sort(mods) 328 var buf bytes.Buffer 329 for _, m := range mods { 330 list := goSum.m[m] 331 sort.Strings(list) 332 for _, h := range list { 333 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) 334 } 335 } 336 337 data, _ := ioutil.ReadFile(GoSumFile) 338 if !bytes.Equal(data, buf.Bytes()) { 339 if err := ioutil.WriteFile(GoSumFile, buf.Bytes(), 0666); err != nil { 340 base.Fatalf("go: writing go.sum: %v", err) 341 } 342 } 343 344 if goSum.modverify != "" { 345 os.Remove(goSum.modverify) 346 } 347 } 348 349 // TrimGoSum trims go.sum to contain only the modules for which keep[m] is true. 350 func TrimGoSum(keep map[module.Version]bool) { 351 goSum.mu.Lock() 352 defer goSum.mu.Unlock() 353 if !initGoSum() { 354 return 355 } 356 357 for m := range goSum.m { 358 // If we're keeping x@v we also keep x@v/go.mod. 359 // Map x@v/go.mod back to x@v for the keep lookup. 360 noGoMod := module.Version{Path: m.Path, Version: strings.TrimSuffix(m.Version, "/go.mod")} 361 if !keep[m] && !keep[noGoMod] { 362 delete(goSum.m, m) 363 } 364 } 365 }