1
2
3
4
5
6 package cache
7
8 import (
9 "bytes"
10 "crypto/sha256"
11 "encoding/hex"
12 "errors"
13 "fmt"
14 "internal/godebug"
15 "io"
16 "io/fs"
17 "os"
18 "path/filepath"
19 "strconv"
20 "strings"
21 "time"
22
23 "cmd/go/internal/lockedfile"
24 "cmd/go/internal/mmap"
25 )
26
27
28
29
30 type ActionID [HashSize]byte
31
32
33 type OutputID [HashSize]byte
34
35
36 type Cache interface {
37
38
39
40
41
42 Get(ActionID) (Entry, error)
43
44
45
46
47
48
49
50
51
52
53
54 Put(ActionID, io.ReadSeeker) (_ OutputID, size int64, _ error)
55
56
57
58
59
60
61
62
63
64 Close() error
65
66
67
68
69
70
71 OutputFile(OutputID) string
72
73
74 FuzzDir() string
75 }
76
77
78 type DiskCache struct {
79 dir string
80 now func() time.Time
81 }
82
83
84
85
86
87
88
89
90
91
92
93
94 func Open(dir string) (*DiskCache, error) {
95 info, err := os.Stat(dir)
96 if err != nil {
97 return nil, err
98 }
99 if !info.IsDir() {
100 return nil, &fs.PathError{Op: "open", Path: dir, Err: fmt.Errorf("not a directory")}
101 }
102 for i := 0; i < 256; i++ {
103 name := filepath.Join(dir, fmt.Sprintf("%02x", i))
104 if err := os.MkdirAll(name, 0777); err != nil {
105 return nil, err
106 }
107 }
108 c := &DiskCache{
109 dir: dir,
110 now: time.Now,
111 }
112 return c, nil
113 }
114
115
116 func (c *DiskCache) fileName(id [HashSize]byte, key string) string {
117 return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key)
118 }
119
120
121
122 type entryNotFoundError struct {
123 Err error
124 }
125
126 func (e *entryNotFoundError) Error() string {
127 if e.Err == nil {
128 return "cache entry not found"
129 }
130 return fmt.Sprintf("cache entry not found: %v", e.Err)
131 }
132
133 func (e *entryNotFoundError) Unwrap() error {
134 return e.Err
135 }
136
137 const (
138
139 hexSize = HashSize * 2
140 entrySize = 2 + 1 + hexSize + 1 + hexSize + 1 + 20 + 1 + 20 + 1
141 )
142
143
144
145
146
147
148
149
150
151
152 var verify = false
153
154 var errVerifyMode = errors.New("gocacheverify=1")
155
156
157 var DebugTest = false
158
159 func init() { initEnv() }
160
161 var (
162 gocacheverify = godebug.New("gocacheverify")
163 gocachehash = godebug.New("gocachehash")
164 gocachetest = godebug.New("gocachetest")
165 )
166
167 func initEnv() {
168 if gocacheverify.Value() == "1" {
169 gocacheverify.IncNonDefault()
170 verify = true
171 }
172 if gocachehash.Value() == "1" {
173 gocachehash.IncNonDefault()
174 debugHash = true
175 }
176 if gocachetest.Value() == "1" {
177 gocachetest.IncNonDefault()
178 DebugTest = true
179 }
180 }
181
182
183
184
185
186 func (c *DiskCache) Get(id ActionID) (Entry, error) {
187 if verify {
188 return Entry{}, &entryNotFoundError{Err: errVerifyMode}
189 }
190 return c.get(id)
191 }
192
193 type Entry struct {
194 OutputID OutputID
195 Size int64
196 Time time.Time
197 }
198
199
200 func (c *DiskCache) get(id ActionID) (Entry, error) {
201 missing := func(reason error) (Entry, error) {
202 return Entry{}, &entryNotFoundError{Err: reason}
203 }
204 f, err := os.Open(c.fileName(id, "a"))
205 if err != nil {
206 return missing(err)
207 }
208 defer f.Close()
209 entry := make([]byte, entrySize+1)
210 if n, err := io.ReadFull(f, entry); n > entrySize {
211 return missing(errors.New("too long"))
212 } else if err != io.ErrUnexpectedEOF {
213 if err == io.EOF {
214 return missing(errors.New("file is empty"))
215 }
216 return missing(err)
217 } else if n < entrySize {
218 return missing(errors.New("entry file incomplete"))
219 }
220 if entry[0] != 'v' || entry[1] != '1' || entry[2] != ' ' || entry[3+hexSize] != ' ' || entry[3+hexSize+1+hexSize] != ' ' || entry[3+hexSize+1+hexSize+1+20] != ' ' || entry[entrySize-1] != '\n' {
221 return missing(errors.New("invalid header"))
222 }
223 eid, entry := entry[3:3+hexSize], entry[3+hexSize:]
224 eout, entry := entry[1:1+hexSize], entry[1+hexSize:]
225 esize, entry := entry[1:1+20], entry[1+20:]
226 etime, entry := entry[1:1+20], entry[1+20:]
227 var buf [HashSize]byte
228 if _, err := hex.Decode(buf[:], eid); err != nil {
229 return missing(fmt.Errorf("decoding ID: %v", err))
230 } else if buf != id {
231 return missing(errors.New("mismatched ID"))
232 }
233 if _, err := hex.Decode(buf[:], eout); err != nil {
234 return missing(fmt.Errorf("decoding output ID: %v", err))
235 }
236 i := 0
237 for i < len(esize) && esize[i] == ' ' {
238 i++
239 }
240 size, err := strconv.ParseInt(string(esize[i:]), 10, 64)
241 if err != nil {
242 return missing(fmt.Errorf("parsing size: %v", err))
243 } else if size < 0 {
244 return missing(errors.New("negative size"))
245 }
246 i = 0
247 for i < len(etime) && etime[i] == ' ' {
248 i++
249 }
250 tm, err := strconv.ParseInt(string(etime[i:]), 10, 64)
251 if err != nil {
252 return missing(fmt.Errorf("parsing timestamp: %v", err))
253 } else if tm < 0 {
254 return missing(errors.New("negative timestamp"))
255 }
256
257 c.used(c.fileName(id, "a"))
258
259 return Entry{buf, size, time.Unix(0, tm)}, nil
260 }
261
262
263
264 func GetFile(c Cache, id ActionID) (file string, entry Entry, err error) {
265 entry, err = c.Get(id)
266 if err != nil {
267 return "", Entry{}, err
268 }
269 file = c.OutputFile(entry.OutputID)
270 info, err := os.Stat(file)
271 if err != nil {
272 return "", Entry{}, &entryNotFoundError{Err: err}
273 }
274 if info.Size() != entry.Size {
275 return "", Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")}
276 }
277 return file, entry, nil
278 }
279
280
281
282
283 func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) {
284 entry, err := c.Get(id)
285 if err != nil {
286 return nil, entry, err
287 }
288 data, _ := os.ReadFile(c.OutputFile(entry.OutputID))
289 if sha256.Sum256(data) != entry.OutputID {
290 return nil, entry, &entryNotFoundError{Err: errors.New("bad checksum")}
291 }
292 return data, entry, nil
293 }
294
295
296
297
298 func GetMmap(c Cache, id ActionID) ([]byte, Entry, error) {
299 entry, err := c.Get(id)
300 if err != nil {
301 return nil, entry, err
302 }
303 md, err := mmap.Mmap(c.OutputFile(entry.OutputID))
304 if err != nil {
305 return nil, Entry{}, err
306 }
307 if int64(len(md.Data)) != entry.Size {
308 return nil, Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")}
309 }
310 return md.Data, entry, nil
311 }
312
313
314 func (c *DiskCache) OutputFile(out OutputID) string {
315 file := c.fileName(out, "d")
316 c.used(file)
317 return file
318 }
319
320
321
322
323
324
325
326
327
328
329
330
331
332 const (
333 mtimeInterval = 1 * time.Hour
334 trimInterval = 24 * time.Hour
335 trimLimit = 5 * 24 * time.Hour
336 )
337
338
339
340
341
342
343
344
345
346
347 func (c *DiskCache) used(file string) {
348 info, err := os.Stat(file)
349 if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval {
350 return
351 }
352 os.Chtimes(file, c.now(), c.now())
353 }
354
355 func (c *DiskCache) Close() error { return c.Trim() }
356
357
358 func (c *DiskCache) Trim() error {
359 now := c.now()
360
361
362
363
364
365
366
367
368 if data, err := lockedfile.Read(filepath.Join(c.dir, "trim.txt")); err == nil {
369 if t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64); err == nil {
370 lastTrim := time.Unix(t, 0)
371 if d := now.Sub(lastTrim); d < trimInterval && d > -mtimeInterval {
372 return nil
373 }
374 }
375 }
376
377
378
379
380 cutoff := now.Add(-trimLimit - mtimeInterval)
381 for i := 0; i < 256; i++ {
382 subdir := filepath.Join(c.dir, fmt.Sprintf("%02x", i))
383 c.trimSubdir(subdir, cutoff)
384 }
385
386
387
388 var b bytes.Buffer
389 fmt.Fprintf(&b, "%d", now.Unix())
390 if err := lockedfile.Write(filepath.Join(c.dir, "trim.txt"), &b, 0666); err != nil {
391 return err
392 }
393
394 return nil
395 }
396
397
398 func (c *DiskCache) trimSubdir(subdir string, cutoff time.Time) {
399
400
401
402
403
404 f, err := os.Open(subdir)
405 if err != nil {
406 return
407 }
408 names, _ := f.Readdirnames(-1)
409 f.Close()
410
411 for _, name := range names {
412
413 if !strings.HasSuffix(name, "-a") && !strings.HasSuffix(name, "-d") {
414 continue
415 }
416 entry := filepath.Join(subdir, name)
417 info, err := os.Stat(entry)
418 if err == nil && info.ModTime().Before(cutoff) {
419 os.Remove(entry)
420 }
421 }
422 }
423
424
425
426 func (c *DiskCache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error {
427
428
429
430
431
432
433
434
435
436
437
438 entry := fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano())
439 if verify && allowVerify {
440 old, err := c.get(id)
441 if err == nil && (old.OutputID != out || old.Size != size) {
442
443 msg := fmt.Sprintf("go: internal cache error: cache verify failed: id=%x changed:<<<\n%s\n>>>\nold: %x %d\nnew: %x %d", id, reverseHash(id), out, size, old.OutputID, old.Size)
444 panic(msg)
445 }
446 }
447 file := c.fileName(id, "a")
448
449
450 mode := os.O_WRONLY | os.O_CREATE
451 f, err := os.OpenFile(file, mode, 0666)
452 if err != nil {
453 return err
454 }
455 _, err = f.WriteString(entry)
456 if err == nil {
457
458
459
460
461
462
463
464 err = f.Truncate(int64(len(entry)))
465 }
466 if closeErr := f.Close(); err == nil {
467 err = closeErr
468 }
469 if err != nil {
470
471
472 os.Remove(file)
473 return err
474 }
475 os.Chtimes(file, c.now(), c.now())
476
477 return nil
478 }
479
480
481
482
483 type noVerifyReadSeeker struct {
484 io.ReadSeeker
485 }
486
487
488
489 func (c *DiskCache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
490 wrapper, isNoVerify := file.(noVerifyReadSeeker)
491 if isNoVerify {
492 file = wrapper.ReadSeeker
493 }
494 return c.put(id, file, !isNoVerify)
495 }
496
497
498
499
500
501 func PutNoVerify(c Cache, id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
502 return c.Put(id, noVerifyReadSeeker{file})
503 }
504
505 func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
506
507 h := sha256.New()
508 if _, err := file.Seek(0, 0); err != nil {
509 return OutputID{}, 0, err
510 }
511 size, err := io.Copy(h, file)
512 if err != nil {
513 return OutputID{}, 0, err
514 }
515 var out OutputID
516 h.Sum(out[:0])
517
518
519 if err := c.copyFile(file, out, size); err != nil {
520 return out, size, err
521 }
522
523
524 return out, size, c.putIndexEntry(id, out, size, allowVerify)
525 }
526
527
528 func PutBytes(c Cache, id ActionID, data []byte) error {
529 _, _, err := c.Put(id, bytes.NewReader(data))
530 return err
531 }
532
533
534
535 func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
536 name := c.fileName(out, "d")
537 info, err := os.Stat(name)
538 if err == nil && info.Size() == size {
539
540 if f, err := os.Open(name); err == nil {
541 h := sha256.New()
542 io.Copy(h, f)
543 f.Close()
544 var out2 OutputID
545 h.Sum(out2[:0])
546 if out == out2 {
547 return nil
548 }
549 }
550
551 }
552
553
554 mode := os.O_RDWR | os.O_CREATE
555 if err == nil && info.Size() > size {
556 mode |= os.O_TRUNC
557 }
558 f, err := os.OpenFile(name, mode, 0666)
559 if err != nil {
560 return err
561 }
562 defer f.Close()
563 if size == 0 {
564
565
566
567 return nil
568 }
569
570
571
572
573
574
575 if _, err := file.Seek(0, 0); err != nil {
576 f.Truncate(0)
577 return err
578 }
579 h := sha256.New()
580 w := io.MultiWriter(f, h)
581 if _, err := io.CopyN(w, file, size-1); err != nil {
582 f.Truncate(0)
583 return err
584 }
585
586
587
588 buf := make([]byte, 1)
589 if _, err := file.Read(buf); err != nil {
590 f.Truncate(0)
591 return err
592 }
593 h.Write(buf)
594 sum := h.Sum(nil)
595 if !bytes.Equal(sum, out[:]) {
596 f.Truncate(0)
597 return fmt.Errorf("file content changed underfoot")
598 }
599
600
601 if _, err := f.Write(buf); err != nil {
602 f.Truncate(0)
603 return err
604 }
605 if err := f.Close(); err != nil {
606
607
608
609 os.Remove(name)
610 return err
611 }
612 os.Chtimes(name, c.now(), c.now())
613
614 return nil
615 }
616
617
618
619
620
621
622
623
624
625 func (c *DiskCache) FuzzDir() string {
626 return filepath.Join(c.dir, "fuzz")
627 }
628
View as plain text