Skip to content

Commit 7038a38

Browse files
Jay Conrodtoothrot
Jay Conrod
authored andcommitted
[release-branch.go1.15] cmd/go/internal/modfetch: detect and recover from missing ziphash file
Previously, if an extracted module directory existed in the module cache, but the corresponding ziphash file did not, if the sum was missing from go.sum, we would not verify the sum. This caused 'go get' not to write missing sums. 'go build' in readonly mode (now the default) checks for missing sums and doesn't attempt to fetch modules that can't be verified against go.sum. With this change, when requesting the module directory with modfetch.DownloadDir, if the ziphash file is missing, the go command will re-hash the zip without downloading or re-extracting it again. Note that the go command creates the ziphash file before the module directory, but another program could remove it separately, and it might not be present after a crash. Fixes #44872 Change-Id: I64551e048a3ba17d069de1ec123d5b8b2757543c Reviewed-on: https://go-review.googlesource.com/c/go/+/298352 Trust: Jay Conrod <[email protected]> Run-TryBot: Jay Conrod <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]> (cherry picked from commit 302a400) Reviewed-on: https://go-review.googlesource.com/c/go/+/299830
1 parent dcffdac commit 7038a38

File tree

4 files changed

+125
-31
lines changed

4 files changed

+125
-31
lines changed

src/cmd/go/internal/modfetch/cache.go

+17
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func DownloadDir(m module.Version) (string, error) {
8383
return "", err
8484
}
8585

86+
// Check whether the directory itself exists.
8687
dir := filepath.Join(cfg.GOMODCACHE, enc+"@"+encVer)
8788
if fi, err := os.Stat(dir); os.IsNotExist(err) {
8889
return dir, err
@@ -91,6 +92,9 @@ func DownloadDir(m module.Version) (string, error) {
9192
} else if !fi.IsDir() {
9293
return dir, &DownloadDirPartialError{dir, errors.New("not a directory")}
9394
}
95+
96+
// Check if a .partial file exists. This is created at the beginning of
97+
// a download and removed after the zip is extracted.
9498
partialPath, err := CachePath(m, "partial")
9599
if err != nil {
96100
return dir, err
@@ -100,6 +104,19 @@ func DownloadDir(m module.Version) (string, error) {
100104
} else if !os.IsNotExist(err) {
101105
return dir, err
102106
}
107+
108+
// Check if a .ziphash file exists. It should be created before the
109+
// zip is extracted, but if it was deleted (by another program?), we need
110+
// to re-calculate it.
111+
ziphashPath, err := CachePath(m, "ziphash")
112+
if err != nil {
113+
return dir, err
114+
}
115+
if _, err := os.Stat(ziphashPath); os.IsNotExist(err) {
116+
return dir, &DownloadDirPartialError{dir, errors.New("ziphash file is missing")}
117+
} else if err != nil {
118+
return dir, err
119+
}
103120
return dir, nil
104121
}
105122

src/cmd/go/internal/modfetch/fetch.go

+48-29
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,16 @@ func DownloadZip(mod module.Version) (zipfile string, err error) {
206206
if err != nil {
207207
return cached{"", err}
208208
}
209+
ziphashfile := zipfile + "hash"
209210

210-
// Skip locking if the zipfile already exists.
211+
// Return without locking if the zip and ziphash files exist.
211212
if _, err := os.Stat(zipfile); err == nil {
212-
return cached{zipfile, nil}
213+
if _, err := os.Stat(ziphashfile); err == nil {
214+
return cached{zipfile, nil}
215+
}
213216
}
214217

215-
// The zip file does not exist. Acquire the lock and create it.
218+
// The zip or ziphash file does not exist. Acquire the lock and create them.
216219
if cfg.CmdName != "mod download" {
217220
fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, mod.Version)
218221
}
@@ -222,14 +225,6 @@ func DownloadZip(mod module.Version) (zipfile string, err error) {
222225
}
223226
defer unlock()
224227

225-
// Double-check that the zipfile was not created while we were waiting for
226-
// the lock.
227-
if _, err := os.Stat(zipfile); err == nil {
228-
return cached{zipfile, nil}
229-
}
230-
if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil {
231-
return cached{"", err}
232-
}
233228
if err := downloadZip(mod, zipfile); err != nil {
234229
return cached{"", err}
235230
}
@@ -239,6 +234,25 @@ func DownloadZip(mod module.Version) (zipfile string, err error) {
239234
}
240235

241236
func downloadZip(mod module.Version, zipfile string) (err error) {
237+
// Double-check that the zipfile was not created while we were waiting for
238+
// the lock in DownloadZip.
239+
ziphashfile := zipfile + "hash"
240+
var zipExists, ziphashExists bool
241+
if _, err := os.Stat(zipfile); err == nil {
242+
zipExists = true
243+
}
244+
if _, err := os.Stat(ziphashfile); err == nil {
245+
ziphashExists = true
246+
}
247+
if zipExists && ziphashExists {
248+
return nil
249+
}
250+
251+
// Create parent directories.
252+
if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil {
253+
return err
254+
}
255+
242256
// Clean up any remaining tempfiles from previous runs.
243257
// This is only safe to do because the lock file ensures that their
244258
// writers are no longer active.
@@ -250,6 +264,12 @@ func downloadZip(mod module.Version, zipfile string) (err error) {
250264
}
251265
}
252266

267+
// If the zip file exists, the ziphash file must have been deleted
268+
// or lost after a file system crash. Re-hash the zip without downloading.
269+
if zipExists {
270+
return hashZip(mod, zipfile, ziphashfile)
271+
}
272+
253273
// From here to the os.Rename call below is functionally almost equivalent to
254274
// renameio.WriteToFile, with one key difference: we want to validate the
255275
// contents of the file (by hashing it) before we commit it. Because the file
@@ -306,15 +326,7 @@ func downloadZip(mod module.Version, zipfile string) (err error) {
306326
}
307327

308328
// Hash the zip file and check the sum before renaming to the final location.
309-
hash, err := dirhash.HashZip(f.Name(), dirhash.DefaultHash)
310-
if err != nil {
311-
return err
312-
}
313-
if err := checkModSum(mod, hash); err != nil {
314-
return err
315-
}
316-
317-
if err := renameio.WriteFile(zipfile+"hash", []byte(hash), 0666); err != nil {
329+
if err := hashZip(mod, f.Name(), ziphashfile); err != nil {
318330
return err
319331
}
320332
if err := os.Rename(f.Name(), zipfile); err != nil {
@@ -326,6 +338,22 @@ func downloadZip(mod module.Version, zipfile string) (err error) {
326338
return nil
327339
}
328340

341+
// hashZip reads the zip file opened in f, then writes the hash to ziphashfile,
342+
// overwriting that file if it exists.
343+
//
344+
// If the hash does not match go.sum (or the sumdb if enabled), hashZip returns
345+
// an error and does not write ziphashfile.
346+
func hashZip(mod module.Version, zipfile, ziphashfile string) error {
347+
hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash)
348+
if err != nil {
349+
return err
350+
}
351+
if err := checkModSum(mod, hash); err != nil {
352+
return err
353+
}
354+
return renameio.WriteFile(ziphashfile, []byte(hash), 0666)
355+
}
356+
329357
// makeDirsReadOnly makes a best-effort attempt to remove write permissions for dir
330358
// and its transitive contents.
331359
func makeDirsReadOnly(dir string) {
@@ -457,22 +485,13 @@ func readGoSum(dst map[module.Version][]string, file string, data []byte) error
457485

458486
// checkMod checks the given module's checksum.
459487
func checkMod(mod module.Version) {
460-
if cfg.GOMODCACHE == "" {
461-
// Do not use current directory.
462-
return
463-
}
464-
465488
// Do the file I/O before acquiring the go.sum lock.
466489
ziphash, err := CachePath(mod, "ziphash")
467490
if err != nil {
468491
base.Fatalf("verifying %v", module.VersionError(mod, err))
469492
}
470493
data, err := renameio.ReadFile(ziphash)
471494
if err != nil {
472-
if errors.Is(err, os.ErrNotExist) {
473-
// This can happen if someone does rm -rf GOPATH/src/cache/download. So it goes.
474-
return
475-
}
476495
base.Fatalf("verifying %v", module.VersionError(mod, err))
477496
}
478497
h := strings.TrimSpace(string(data))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Test that if the module cache contains an extracted source directory but not
2+
# a ziphash, 'go build' complains about a missing sum, and 'go get' adds
3+
# the sum. Verifies #44749.
4+
5+
# With a tidy go.sum, go build succeeds. This also populates the module cache.
6+
cp go.sum.tidy go.sum
7+
go build -n use
8+
env GOPROXY=off
9+
env GOSUMDB=off
10+
11+
# Control case: if we delete the hash for rsc.io/quote v1.5.2,
12+
# 'go build' reports an error. 'go get' adds the sum.
13+
cp go.sum.bug go.sum
14+
! go build -n -mod=readonly use
15+
stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
16+
go get -d use
17+
cmp go.sum go.sum.tidy
18+
go build -n use
19+
20+
# If we delete the hash *and* the ziphash file, we should see the same behavior.
21+
cp go.sum.bug go.sum
22+
rm $WORK/gopath/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.ziphash
23+
! go build -n -mod=readonly use
24+
stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
25+
go get -d use
26+
cmp go.sum go.sum.tidy
27+
go build -n use
28+
29+
-- go.mod --
30+
module use
31+
32+
go 1.17
33+
34+
require rsc.io/quote v1.5.2
35+
-- go.sum.tidy --
36+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:pvCbr/wm8HzDD3fVywevekufpn6tCGPY3spdHeZJEsw=
37+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
38+
rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0=
39+
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
40+
rsc.io/sampler v1.3.0 h1:HLGR/BgEtI3r0uymSP/nl2uPLsUnNJX8toRyhfpBTII=
41+
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
42+
rsc.io/testonly v1.0.0 h1:K/VWHdO+Jv7woUXG0GzVNx1czBXUt3Ib1deaMn+xk64=
43+
rsc.io/testonly v1.0.0/go.mod h1:OqmGbIFOcF+XrFReLOGZ6BhMM7uMBiQwZsyNmh74SzY=
44+
-- go.sum.bug --
45+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:pvCbr/wm8HzDD3fVywevekufpn6tCGPY3spdHeZJEsw=
46+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
47+
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
48+
rsc.io/sampler v1.3.0 h1:HLGR/BgEtI3r0uymSP/nl2uPLsUnNJX8toRyhfpBTII=
49+
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
50+
rsc.io/testonly v1.0.0 h1:K/VWHdO+Jv7woUXG0GzVNx1czBXUt3Ib1deaMn+xk64=
51+
rsc.io/testonly v1.0.0/go.mod h1:OqmGbIFOcF+XrFReLOGZ6BhMM7uMBiQwZsyNmh74SzY=
52+
-- use.go --
53+
package use
54+
55+
import _ "rsc.io/quote"

src/cmd/go/testdata/script/mod_verify.txt

+5-2
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,13 @@ go mod tidy
5050
grep '^rsc.io/quote v1.1.0/go.mod ' go.sum
5151
grep '^rsc.io/quote v1.1.0 ' go.sum
5252

53-
# sync should ignore missing ziphash; verify should not
53+
# verify should fail on a missing ziphash. tidy should restore it.
5454
rm $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.1.0.ziphash
55-
go mod tidy
5655
! go mod verify
56+
stderr '^rsc.io/quote v1.1.0: missing ziphash: open '$GOPATH'[/\\]pkg[/\\]mod[/\\]cache[/\\]download[/\\]rsc.io[/\\]quote[/\\]@v[/\\]v1.1.0.ziphash'
57+
go mod tidy
58+
exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.1.0.ziphash
59+
go mod verify
5760

5861
# Packages below module root should not be mentioned in go.sum.
5962
rm go.sum

0 commit comments

Comments
 (0)