@@ -23,9 +23,10 @@ import (
23
23
"time"
24
24
25
25
"github.com/rogpeppe/go-internal/lockedfile"
26
+ "github.com/rogpeppe/go-internal/robustio"
26
27
28
+ "github.com/golangci/golangci-lint/v2/internal/go/base"
27
29
"github.com/golangci/golangci-lint/v2/internal/go/mmap"
28
- "github.com/golangci/golangci-lint/v2/internal/go/robustio"
29
30
)
30
31
31
32
// An ActionID is a cache action key, the hash of a complete description of a
@@ -41,8 +42,8 @@ type Cache interface {
41
42
// Get returns the cache entry for the provided ActionID.
42
43
// On miss, the error type should be of type *entryNotFoundError.
43
44
//
44
- // After a success call to Get, OutputFile(Entry.OutputID) must
45
- // exist on disk for until Close is called (at the end of the process).
45
+ // After a successful call to Get, OutputFile(Entry.OutputID) must
46
+ // exist on disk until Close is called (at the end of the process).
46
47
Get (ActionID ) (Entry , error )
47
48
48
49
// Put adds an item to the cache.
@@ -53,14 +54,14 @@ type Cache interface {
53
54
// As a special case, if the ReadSeeker is of type noVerifyReadSeeker,
54
55
// the verification from GODEBUG=goverifycache=1 is skipped.
55
56
//
56
- // After a success call to Get , OutputFile(Entry. OutputID) must
57
- // exist on disk for until Close is called (at the end of the process).
57
+ // After a successful call to Put , OutputFile(OutputID) must
58
+ // exist on disk until Close is called (at the end of the process).
58
59
Put (ActionID , io.ReadSeeker ) (_ OutputID , size int64 , _ error )
59
60
60
61
// Close is called at the end of the go process. Implementations can do
61
62
// cache cleanup work at this phase, or wait for and report any errors from
62
- // background cleanup work started earlier. Any cache trimming should in one
63
- // process should not violate cause the invariants of this interface to be
63
+ // background cleanup work started earlier. Any cache trimming in one
64
+ // process should not cause the invariants of this interface to be
64
65
// violated in another process. Namely, a cache trim from one process should
65
66
// not delete an ObjectID from disk that was recently Get or Put from
66
67
// another process. As a rule of thumb, don't trim things used in the last
@@ -105,7 +106,7 @@ func Open(dir string) (*DiskCache, error) {
105
106
}
106
107
for i := 0 ; i < 256 ; i ++ {
107
108
name := filepath .Join (dir , fmt .Sprintf ("%02x" , i ))
108
- if err := os .MkdirAll (name , 0744 ); err != nil {
109
+ if err := os .MkdirAll (name , 0o777 ); err != nil {
109
110
return nil , err
110
111
}
111
112
}
@@ -161,13 +162,13 @@ var errVerifyMode = errors.New("gocacheverify=1")
161
162
var DebugTest = false
162
163
163
164
// func init() { initEnv() }
164
-
165
+ //
165
166
// var (
166
167
// gocacheverify = godebug.New("gocacheverify")
167
168
// gocachehash = godebug.New("gocachehash")
168
169
// gocachetest = godebug.New("gocachetest")
169
170
// )
170
-
171
+ //
171
172
// func initEnv() {
172
173
// if gocacheverify.Value() == "1" {
173
174
// gocacheverify.IncNonDefault()
@@ -258,10 +259,7 @@ func (c *DiskCache) get(id ActionID) (Entry, error) {
258
259
return missing (errors .New ("negative timestamp" ))
259
260
}
260
261
261
- err = c .used (c .fileName (id , "a" ))
262
- if err != nil {
263
- return Entry {}, fmt .Errorf ("failed to mark %s as used: %w" , c .fileName (id , "a" ), err )
264
- }
262
+ c .markUsed (c .fileName (id , "a" ))
265
263
266
264
return Entry {buf , size , time .Unix (0 , tm )}, nil
267
265
}
@@ -305,25 +303,35 @@ func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) {
305
303
// GetMmap looks up the action ID in the cache and returns
306
304
// the corresponding output bytes.
307
305
// GetMmap should only be used for data that can be expected to fit in memory.
308
- func GetMmap (c Cache , id ActionID ) ([]byte , Entry , error ) {
306
+ func GetMmap (c Cache , id ActionID ) ([]byte , Entry , bool , error ) {
309
307
entry , err := c .Get (id )
310
308
if err != nil {
311
- return nil , entry , err
309
+ return nil , entry , false , err
312
310
}
313
- md , err := mmap .Mmap (c .OutputFile (entry .OutputID ))
311
+ md , opened , err := mmap .Mmap (c .OutputFile (entry .OutputID ))
314
312
if err != nil {
315
- return nil , Entry {}, err
313
+ return nil , Entry {}, opened , err
316
314
}
317
315
if int64 (len (md .Data )) != entry .Size {
318
- return nil , Entry {}, & entryNotFoundError {Err : errors .New ("file incomplete" )}
316
+ return nil , Entry {}, true , & entryNotFoundError {Err : errors .New ("file incomplete" )}
319
317
}
320
- return md .Data , entry , nil
318
+ return md .Data , entry , true , nil
321
319
}
322
320
323
321
// OutputFile returns the name of the cache file storing output with the given OutputID.
324
322
func (c * DiskCache ) OutputFile (out OutputID ) string {
325
323
file := c .fileName (out , "d" )
326
- c .used (file )
324
+ isDir := c .markUsed (file )
325
+ if isDir { // => cached executable
326
+ entries , err := os .ReadDir (file )
327
+ if err != nil {
328
+ return fmt .Sprintf ("DO NOT USE - missing binary cache entry: %v" , err )
329
+ }
330
+ if len (entries ) != 1 {
331
+ return "DO NOT USE - invalid binary cache entry"
332
+ }
333
+ return filepath .Join (file , entries [0 ].Name ())
334
+ }
327
335
return file
328
336
}
329
337
@@ -345,7 +353,7 @@ const (
345
353
trimLimit = 5 * 24 * time .Hour
346
354
)
347
355
348
- // used makes a best-effort attempt to update mtime on file,
356
+ // markUsed makes a best-effort attempt to update mtime on file,
349
357
// so that mtime reflects cache access time.
350
358
//
351
359
// Because the reflection only needs to be approximate,
@@ -354,25 +362,17 @@ const (
354
362
// mtime is more than an hour old. This heuristic eliminates
355
363
// nearly all of the mtime updates that would otherwise happen,
356
364
// while still keeping the mtimes useful for cache trimming.
357
- func (c * DiskCache ) used (file string ) error {
365
+ //
366
+ // markUsed reports whether the file is a directory (an executable cache entry).
367
+ func (c * DiskCache ) markUsed (file string ) (isDir bool ) {
358
368
info , err := os .Stat (file )
359
- if err == nil && c .now ().Sub (info .ModTime ()) < mtimeInterval {
360
- return nil
361
- }
362
-
363
369
if err != nil {
364
- if os .IsNotExist (err ) {
365
- return & entryNotFoundError {Err : err }
366
- }
367
- return & entryNotFoundError {Err : fmt .Errorf ("failed to stat file %s: %w" , file , err )}
370
+ return false
368
371
}
369
-
370
- err = os .Chtimes (file , c .now (), c .now ())
371
- if err != nil {
372
- return fmt .Errorf ("failed to change time of file %s: %w" , file , err )
372
+ if now := c .now (); now .Sub (info .ModTime ()) >= mtimeInterval {
373
+ os .Chtimes (file , now , now )
373
374
}
374
-
375
- return nil
375
+ return info .IsDir ()
376
376
}
377
377
378
378
func (c * DiskCache ) Close () error { return c .Trim () }
@@ -410,7 +410,7 @@ func (c *DiskCache) Trim() error {
410
410
// cache will appear older than it is, and we'll trim it again next time.
411
411
var b bytes.Buffer
412
412
fmt .Fprintf (& b , "%d" , now .Unix ())
413
- if err := lockedfile .Write (filepath .Join (c .dir , "trim.txt" ), & b , 0666 ); err != nil {
413
+ if err := lockedfile .Write (filepath .Join (c .dir , "trim.txt" ), & b , 0o666 ); err != nil {
414
414
return err
415
415
}
416
416
@@ -439,6 +439,10 @@ func (c *DiskCache) trimSubdir(subdir string, cutoff time.Time) {
439
439
entry := filepath .Join (subdir , name )
440
440
info , err := os .Stat (entry )
441
441
if err == nil && info .ModTime ().Before (cutoff ) {
442
+ if info .IsDir () { // executable cache entry
443
+ os .RemoveAll (entry )
444
+ continue
445
+ }
442
446
os .Remove (entry )
443
447
}
444
448
}
@@ -471,7 +475,7 @@ func (c *DiskCache) putIndexEntry(id ActionID, out OutputID, size int64, allowVe
471
475
472
476
// Copy file to cache directory.
473
477
mode := os .O_WRONLY | os .O_CREATE
474
- f , err := os .OpenFile (file , mode , 0666 )
478
+ f , err := os .OpenFile (file , mode , 0o666 )
475
479
if err != nil {
476
480
return err
477
481
}
@@ -517,7 +521,21 @@ func (c *DiskCache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error
517
521
if isNoVerify {
518
522
file = wrapper .ReadSeeker
519
523
}
520
- return c .put (id , file , ! isNoVerify )
524
+ return c .put (id , "" , file , ! isNoVerify )
525
+ }
526
+
527
+ // PutExecutable is used to store the output as the output for the action ID into a
528
+ // file with the given base name, with the executable mode bit set.
529
+ // It may read file twice. The content of file must not change between the two passes.
530
+ func (c * DiskCache ) PutExecutable (id ActionID , name string , file io.ReadSeeker ) (OutputID , int64 , error ) {
531
+ if name == "" {
532
+ panic ("PutExecutable called without a name" )
533
+ }
534
+ wrapper , isNoVerify := file .(noVerifyReadSeeker )
535
+ if isNoVerify {
536
+ file = wrapper .ReadSeeker
537
+ }
538
+ return c .put (id , name , file , ! isNoVerify )
521
539
}
522
540
523
541
// PutNoVerify is like Put but disables the verify check
@@ -528,7 +546,7 @@ func PutNoVerify(c Cache, id ActionID, file io.ReadSeeker) (OutputID, int64, err
528
546
return c .Put (id , noVerifyReadSeeker {file })
529
547
}
530
548
531
- func (c * DiskCache ) put (id ActionID , file io.ReadSeeker , allowVerify bool ) (OutputID , int64 , error ) {
549
+ func (c * DiskCache ) put (id ActionID , executableName string , file io.ReadSeeker , allowVerify bool ) (OutputID , int64 , error ) {
532
550
// Compute output ID.
533
551
h := sha256 .New ()
534
552
if _ , err := file .Seek (0 , 0 ); err != nil {
@@ -542,7 +560,11 @@ func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (Outp
542
560
h .Sum (out [:0 ])
543
561
544
562
// Copy to cached output file (if not already present).
545
- if err := c .copyFile (file , out , size ); err != nil {
563
+ fileMode := fs .FileMode (0o666 )
564
+ if executableName != "" {
565
+ fileMode = 0o777
566
+ }
567
+ if err := c .copyFile (file , executableName , out , size , fileMode ); err != nil {
546
568
return out , size , err
547
569
}
548
570
@@ -558,9 +580,33 @@ func PutBytes(c Cache, id ActionID, data []byte) error {
558
580
559
581
// copyFile copies file into the cache, expecting it to have the given
560
582
// output ID and size, if that file is not present already.
561
- func (c * DiskCache ) copyFile (file io.ReadSeeker , out OutputID , size int64 ) error {
562
- name := c .fileName (out , "d" )
583
+ func (c * DiskCache ) copyFile (file io.ReadSeeker , executableName string , out OutputID , size int64 , perm os. FileMode ) error {
584
+ name := c .fileName (out , "d" ) // TODO(matloob): use a different suffix for the executable cache?
563
585
info , err := os .Stat (name )
586
+ if executableName != "" {
587
+ // This is an executable file. The file at name won't hold the output itself, but will
588
+ // be a directory that holds the output, named according to executableName. Check to see
589
+ // if the directory already exists, and if it does not, create it. Then reset name
590
+ // to the name we want the output written to.
591
+ if err != nil {
592
+ if ! os .IsNotExist (err ) {
593
+ return err
594
+ }
595
+ if err := os .Mkdir (name , 0o777 ); err != nil {
596
+ return err
597
+ }
598
+ if info , err = os .Stat (name ); err != nil {
599
+ return err
600
+ }
601
+ }
602
+ if ! info .IsDir () {
603
+ return errors .New ("internal error: invalid binary cache entry: not a directory" )
604
+ }
605
+
606
+ // directory exists. now set name to the inner file
607
+ name = filepath .Join (name , executableName )
608
+ info , err = os .Stat (name )
609
+ }
564
610
if err == nil && info .Size () == size {
565
611
// Check hash.
566
612
if f , err := os .Open (name ); err == nil {
@@ -585,8 +631,14 @@ func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error
585
631
if err == nil && info .Size () > size { // shouldn't happen but fix in case
586
632
mode |= os .O_TRUNC
587
633
}
588
- f , err := os .OpenFile (name , mode , 0666 )
634
+ f , err := os .OpenFile (name , mode , perm )
589
635
if err != nil {
636
+ if base .IsETXTBSY (err ) {
637
+ // This file is being used by an executable. It must have
638
+ // already been written by another go process and then run.
639
+ // return without an error.
640
+ return nil
641
+ }
590
642
return err
591
643
}
592
644
defer f .Close ()
0 commit comments