Source file src/pkg/cmd/go/internal/cache/cache.go
1
2
3
4
5
6 package cache
7
8 import (
9 "bytes"
10 "crypto/sha256"
11 "encoding/hex"
12 "errors"
13 "fmt"
14 "io"
15 "io/ioutil"
16 "os"
17 "path/filepath"
18 "strconv"
19 "strings"
20 "time"
21
22 "cmd/go/internal/renameio"
23 )
24
25
26
27
28 type ActionID [HashSize]byte
29
30
31 type OutputID [HashSize]byte
32
33
34 type Cache struct {
35 dir string
36 now func() time.Time
37 }
38
39
40
41
42
43
44
45
46
47
48
49
50
51 func Open(dir string) (*Cache, error) {
52 info, err := os.Stat(dir)
53 if err != nil {
54 return nil, err
55 }
56 if !info.IsDir() {
57 return nil, &os.PathError{Op: "open", Path: dir, Err: fmt.Errorf("not a directory")}
58 }
59 for i := 0; i < 256; i++ {
60 name := filepath.Join(dir, fmt.Sprintf("%02x", i))
61 if err := os.MkdirAll(name, 0777); err != nil {
62 return nil, err
63 }
64 }
65 c := &Cache{
66 dir: dir,
67 now: time.Now,
68 }
69 return c, nil
70 }
71
72
73 func (c *Cache) fileName(id [HashSize]byte, key string) string {
74 return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key)
75 }
76
77 var errMissing = errors.New("cache entry not found")
78
79 const (
80
81 hexSize = HashSize * 2
82 entrySize = 2 + 1 + hexSize + 1 + hexSize + 1 + 20 + 1 + 20 + 1
83 )
84
85
86
87
88
89
90
91
92
93
94 var verify = false
95
96
97 var DebugTest = false
98
99 func init() { initEnv() }
100
101 func initEnv() {
102 verify = false
103 debugHash = false
104 debug := strings.Split(os.Getenv("GODEBUG"), ",")
105 for _, f := range debug {
106 if f == "gocacheverify=1" {
107 verify = true
108 }
109 if f == "gocachehash=1" {
110 debugHash = true
111 }
112 if f == "gocachetest=1" {
113 DebugTest = true
114 }
115 }
116 }
117
118
119
120
121
122 func (c *Cache) Get(id ActionID) (Entry, error) {
123 if verify {
124 return Entry{}, errMissing
125 }
126 return c.get(id)
127 }
128
129 type Entry struct {
130 OutputID OutputID
131 Size int64
132 Time time.Time
133 }
134
135
136 func (c *Cache) get(id ActionID) (Entry, error) {
137 missing := func() (Entry, error) {
138 return Entry{}, errMissing
139 }
140 f, err := os.Open(c.fileName(id, "a"))
141 if err != nil {
142 return missing()
143 }
144 defer f.Close()
145 entry := make([]byte, entrySize+1)
146 if n, err := io.ReadFull(f, entry); n != entrySize || err != io.ErrUnexpectedEOF {
147 return missing()
148 }
149 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' {
150 return missing()
151 }
152 eid, entry := entry[3:3+hexSize], entry[3+hexSize:]
153 eout, entry := entry[1:1+hexSize], entry[1+hexSize:]
154 esize, entry := entry[1:1+20], entry[1+20:]
155 etime, entry := entry[1:1+20], entry[1+20:]
156 var buf [HashSize]byte
157 if _, err := hex.Decode(buf[:], eid); err != nil || buf != id {
158 return missing()
159 }
160 if _, err := hex.Decode(buf[:], eout); err != nil {
161 return missing()
162 }
163 i := 0
164 for i < len(esize) && esize[i] == ' ' {
165 i++
166 }
167 size, err := strconv.ParseInt(string(esize[i:]), 10, 64)
168 if err != nil || size < 0 {
169 return missing()
170 }
171 i = 0
172 for i < len(etime) && etime[i] == ' ' {
173 i++
174 }
175 tm, err := strconv.ParseInt(string(etime[i:]), 10, 64)
176 if err != nil || tm < 0 {
177 return missing()
178 }
179
180 c.used(c.fileName(id, "a"))
181
182 return Entry{buf, size, time.Unix(0, tm)}, nil
183 }
184
185
186
187 func (c *Cache) GetFile(id ActionID) (file string, entry Entry, err error) {
188 entry, err = c.Get(id)
189 if err != nil {
190 return "", Entry{}, err
191 }
192 file = c.OutputFile(entry.OutputID)
193 info, err := os.Stat(file)
194 if err != nil || info.Size() != entry.Size {
195 return "", Entry{}, errMissing
196 }
197 return file, entry, nil
198 }
199
200
201
202
203 func (c *Cache) GetBytes(id ActionID) ([]byte, Entry, error) {
204 entry, err := c.Get(id)
205 if err != nil {
206 return nil, entry, err
207 }
208 data, _ := ioutil.ReadFile(c.OutputFile(entry.OutputID))
209 if sha256.Sum256(data) != entry.OutputID {
210 return nil, entry, errMissing
211 }
212 return data, entry, nil
213 }
214
215
216 func (c *Cache) OutputFile(out OutputID) string {
217 file := c.fileName(out, "d")
218 c.used(file)
219 return file
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234 const (
235 mtimeInterval = 1 * time.Hour
236 trimInterval = 24 * time.Hour
237 trimLimit = 5 * 24 * time.Hour
238 )
239
240
241
242
243
244
245
246
247
248
249 func (c *Cache) used(file string) {
250 info, err := os.Stat(file)
251 if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval {
252 return
253 }
254 os.Chtimes(file, c.now(), c.now())
255 }
256
257
258 func (c *Cache) Trim() {
259 now := c.now()
260
261
262
263
264 data, _ := renameio.ReadFile(filepath.Join(c.dir, "trim.txt"))
265 t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
266 if err == nil && now.Sub(time.Unix(t, 0)) < trimInterval {
267 return
268 }
269
270
271
272
273 cutoff := now.Add(-trimLimit - mtimeInterval)
274 for i := 0; i < 256; i++ {
275 subdir := filepath.Join(c.dir, fmt.Sprintf("%02x", i))
276 c.trimSubdir(subdir, cutoff)
277 }
278
279
280
281 renameio.WriteFile(filepath.Join(c.dir, "trim.txt"), []byte(fmt.Sprintf("%d", now.Unix())), 0666)
282 }
283
284
285 func (c *Cache) trimSubdir(subdir string, cutoff time.Time) {
286
287
288
289
290
291 f, err := os.Open(subdir)
292 if err != nil {
293 return
294 }
295 names, _ := f.Readdirnames(-1)
296 f.Close()
297
298 for _, name := range names {
299
300 if !strings.HasSuffix(name, "-a") && !strings.HasSuffix(name, "-d") {
301 continue
302 }
303 entry := filepath.Join(subdir, name)
304 info, err := os.Stat(entry)
305 if err == nil && info.ModTime().Before(cutoff) {
306 os.Remove(entry)
307 }
308 }
309 }
310
311
312
313 func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error {
314
315
316
317
318
319
320
321
322
323
324
325 entry := fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano())
326 if verify && allowVerify {
327 old, err := c.get(id)
328 if err == nil && (old.OutputID != out || old.Size != size) {
329
330 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)
331 panic(msg)
332 }
333 }
334 file := c.fileName(id, "a")
335
336
337 mode := os.O_WRONLY | os.O_CREATE
338 f, err := os.OpenFile(file, mode, 0666)
339 if err != nil {
340 return err
341 }
342 _, err = f.WriteString(entry)
343 if err == nil {
344
345
346
347
348
349
350
351 err = f.Truncate(int64(len(entry)))
352 }
353 if closeErr := f.Close(); err == nil {
354 err = closeErr
355 }
356 if err != nil {
357
358
359 os.Remove(file)
360 return err
361 }
362 os.Chtimes(file, c.now(), c.now())
363
364 return nil
365 }
366
367
368
369 func (c *Cache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
370 return c.put(id, file, true)
371 }
372
373
374
375
376
377 func (c *Cache) PutNoVerify(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
378 return c.put(id, file, false)
379 }
380
381 func (c *Cache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
382
383 h := sha256.New()
384 if _, err := file.Seek(0, 0); err != nil {
385 return OutputID{}, 0, err
386 }
387 size, err := io.Copy(h, file)
388 if err != nil {
389 return OutputID{}, 0, err
390 }
391 var out OutputID
392 h.Sum(out[:0])
393
394
395 if err := c.copyFile(file, out, size); err != nil {
396 return out, size, err
397 }
398
399
400 return out, size, c.putIndexEntry(id, out, size, allowVerify)
401 }
402
403
404 func (c *Cache) PutBytes(id ActionID, data []byte) error {
405 _, _, err := c.Put(id, bytes.NewReader(data))
406 return err
407 }
408
409
410
411 func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
412 name := c.fileName(out, "d")
413 info, err := os.Stat(name)
414 if err == nil && info.Size() == size {
415
416 if f, err := os.Open(name); err == nil {
417 h := sha256.New()
418 io.Copy(h, f)
419 f.Close()
420 var out2 OutputID
421 h.Sum(out2[:0])
422 if out == out2 {
423 return nil
424 }
425 }
426
427 }
428
429
430 mode := os.O_RDWR | os.O_CREATE
431 if err == nil && info.Size() > size {
432 mode |= os.O_TRUNC
433 }
434 f, err := os.OpenFile(name, mode, 0666)
435 if err != nil {
436 return err
437 }
438 defer f.Close()
439 if size == 0 {
440
441
442
443 return nil
444 }
445
446
447
448
449
450
451 if _, err := file.Seek(0, 0); err != nil {
452 f.Truncate(0)
453 return err
454 }
455 h := sha256.New()
456 w := io.MultiWriter(f, h)
457 if _, err := io.CopyN(w, file, size-1); err != nil {
458 f.Truncate(0)
459 return err
460 }
461
462
463
464 buf := make([]byte, 1)
465 if _, err := file.Read(buf); err != nil {
466 f.Truncate(0)
467 return err
468 }
469 h.Write(buf)
470 sum := h.Sum(nil)
471 if !bytes.Equal(sum, out[:]) {
472 f.Truncate(0)
473 return fmt.Errorf("file content changed underfoot")
474 }
475
476
477 if _, err := f.Write(buf); err != nil {
478 f.Truncate(0)
479 return err
480 }
481 if err := f.Close(); err != nil {
482
483
484
485 os.Remove(name)
486 return err
487 }
488 os.Chtimes(name, c.now(), c.now())
489
490 return nil
491 }
492
View as plain text