@@ -229,6 +229,31 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
229
229
return res , nil
230
230
}
231
231
232
+ shad := cacheDirectoryPath (o .cacheDir , remote )
233
+ if err := os .MkdirAll (shad , 0o700 ); err != nil {
234
+ return nil , err
235
+ }
236
+
237
+ var res * Result
238
+ err := lockutil .WithDirLock (shad , func () error {
239
+ var err error
240
+ res , err = getCached (ctx , localPath , remote , o )
241
+ if err != nil {
242
+ return err
243
+ }
244
+ if res != nil {
245
+ return nil
246
+ }
247
+ res , err = fetch (ctx , localPath , remote , o )
248
+ return err
249
+ })
250
+ return res , err
251
+ }
252
+
253
+ // getCached tries to copy the file from the cache to local path. Return result,
254
+ // nil if the file was copied, nil, nil if the file is not in the cache or the
255
+ // cache needs update, or nil, error on fatal error.
256
+ func getCached (ctx context.Context , localPath , remote string , o options ) (* Result , error ) {
232
257
shad := cacheDirectoryPath (o .cacheDir , remote )
233
258
shadData := filepath .Join (shad , "data" )
234
259
shadTime := filepath .Join (shad , "time" )
@@ -237,53 +262,62 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
237
262
if err != nil {
238
263
return nil , err
239
264
}
240
- if _ , err := os .Stat (shadData ); err == nil {
241
- logrus .Debugf ("file %q is cached as %q" , localPath , shadData )
242
- useCache := true
243
- if _ , err := os .Stat (shadDigest ); err == nil {
244
- logrus .Debugf ("Comparing digest %q with the cached digest file %q, not computing the actual digest of %q" ,
245
- o .expectedDigest , shadDigest , shadData )
246
- if err := validateCachedDigest (shadDigest , o .expectedDigest ); err != nil {
247
- return nil , err
248
- }
249
- if err := copyLocal (ctx , localPath , shadData , ext , o .decompress , "" , "" ); err != nil {
265
+ if _ , err := os .Stat (shadData ); err != nil {
266
+ return nil , nil
267
+ }
268
+ ext := path .Ext (remote )
269
+ logrus .Debugf ("file %q is cached as %q" , localPath , shadData )
270
+ if _ , err := os .Stat (shadDigest ); err == nil {
271
+ logrus .Debugf ("Comparing digest %q with the cached digest file %q, not computing the actual digest of %q" ,
272
+ o .expectedDigest , shadDigest , shadData )
273
+ if err := validateCachedDigest (shadDigest , o .expectedDigest ); err != nil {
274
+ return nil , err
275
+ }
276
+ if err := copyLocal (ctx , localPath , shadData , ext , o .decompress , "" , "" ); err != nil {
277
+ return nil , err
278
+ }
279
+ } else {
280
+ if match , lmCached , lmRemote , err := matchLastModified (ctx , shadTime , remote ); err != nil {
281
+ logrus .WithError (err ).Info ("Failed to retrieve last-modified for cached digest-less image; using cached image." )
282
+ } else if match {
283
+ if err := copyLocal (ctx , localPath , shadData , ext , o .decompress , o .description , o .expectedDigest ); err != nil {
250
284
return nil , err
251
285
}
252
286
} else {
253
- if match , lmCached , lmRemote , err := matchLastModified (ctx , shadTime , remote ); err != nil {
254
- logrus .WithError (err ).Info ("Failed to retrieve last-modified for cached digest-less image; using cached image." )
255
- } else if match {
256
- if err := copyLocal (ctx , localPath , shadData , ext , o .decompress , o .description , o .expectedDigest ); err != nil {
257
- return nil , err
258
- }
259
- } else {
260
- logrus .Infof ("Re-downloading digest-less image: last-modified mismatch (cached: %q, remote: %q)" , lmCached , lmRemote )
261
- useCache = false
262
- }
263
- }
264
- if useCache {
265
- res := & Result {
266
- Status : StatusUsedCache ,
267
- CachePath : shadData ,
268
- LastModified : readTime (shadTime ),
269
- ContentType : readFile (shadType ),
270
- ValidatedDigest : o .expectedDigest != "" ,
271
- }
272
- return res , nil
287
+ logrus .Infof ("Re-downloading digest-less image: last-modified mismatch (cached: %q, remote: %q)" , lmCached , lmRemote )
288
+ return nil , nil
273
289
}
274
290
}
275
- if err := os .MkdirAll (shad , 0o700 ); err != nil {
291
+ res := & Result {
292
+ Status : StatusUsedCache ,
293
+ CachePath : shadData ,
294
+ LastModified : readTime (shadTime ),
295
+ ContentType : readFile (shadType ),
296
+ ValidatedDigest : o .expectedDigest != "" ,
297
+ }
298
+ return res , nil
299
+ }
300
+
301
+ // fetch downloads remote to the cache and copy the cached file to local path.
302
+ func fetch (ctx context.Context , localPath , remote string , o options ) (* Result , error ) {
303
+ shad := cacheDirectoryPath (o .cacheDir , remote )
304
+ shadData := filepath .Join (shad , "data" )
305
+ shadTime := filepath .Join (shad , "time" )
306
+ shadType := filepath .Join (shad , "type" )
307
+ shadDigest , err := cacheDigestPath (shad , o .expectedDigest )
308
+ if err != nil {
276
309
return nil , err
277
310
}
311
+ ext := path .Ext (remote )
278
312
shadURL := filepath .Join (shad , "url" )
279
- if err := writeFirst (shadURL , []byte (remote ), 0o644 ); err != nil {
313
+ if err := os . WriteFile (shadURL , []byte (remote ), 0o644 ); err != nil {
280
314
return nil , err
281
315
}
282
316
if err := downloadHTTP (ctx , shadData , shadTime , shadType , remote , o .description , o .expectedDigest ); err != nil {
283
317
return nil , err
284
318
}
285
319
if shadDigest != "" && o .expectedDigest != "" {
286
- if err := writeFirst (shadDigest , []byte (o .expectedDigest .String ()), 0o644 ); err != nil {
320
+ if err := os . WriteFile (shadDigest , []byte (o .expectedDigest .String ()), 0o644 ); err != nil {
287
321
return nil , err
288
322
}
289
323
}
@@ -327,18 +361,33 @@ func Cached(remote string, opts ...Opt) (*Result, error) {
327
361
if err != nil {
328
362
return nil , err
329
363
}
364
+
365
+ // Checking if data file exists is safe without locking.
330
366
if _ , err := os .Stat (shadData ); err != nil {
331
367
return nil , err
332
368
}
333
- if _ , err := os .Stat (shadDigest ); err != nil {
334
- if err := validateCachedDigest (shadDigest , o .expectedDigest ); err != nil {
335
- return nil , err
336
- }
337
- } else {
338
- if err := validateLocalFileDigest (shadData , o .expectedDigest ); err != nil {
339
- return nil , err
369
+
370
+ // But validating the digest or the data file must take the lock to avoid races
371
+ // with parallel downloads.
372
+ if err := os .MkdirAll (shad , 0o700 ); err != nil {
373
+ return nil , err
374
+ }
375
+ err = lockutil .WithDirLock (shad , func () error {
376
+ if _ , err := os .Stat (shadDigest ); err != nil {
377
+ if err := validateCachedDigest (shadDigest , o .expectedDigest ); err != nil {
378
+ return err
379
+ }
380
+ } else {
381
+ if err := validateLocalFileDigest (shadData , o .expectedDigest ); err != nil {
382
+ return err
383
+ }
340
384
}
385
+ return nil
386
+ })
387
+ if err != nil {
388
+ return nil , err
341
389
}
390
+
342
391
res := & Result {
343
392
Status : StatusUsedCache ,
344
393
CachePath : shadData ,
@@ -612,13 +661,13 @@ func downloadHTTP(ctx context.Context, localPath, lastModified, contentType, url
612
661
}
613
662
if lastModified != "" {
614
663
lm := resp .Header .Get ("Last-Modified" )
615
- if err := writeFirst (lastModified , []byte (lm ), 0o644 ); err != nil {
664
+ if err := os . WriteFile (lastModified , []byte (lm ), 0o644 ); err != nil {
616
665
return err
617
666
}
618
667
}
619
668
if contentType != "" {
620
669
ct := resp .Header .Get ("Content-Type" )
621
- if err := writeFirst (contentType , []byte (ct ), 0o644 ); err != nil {
670
+ if err := os . WriteFile (contentType , []byte (ct ), 0o644 ); err != nil {
622
671
return err
623
672
}
624
673
}
@@ -679,19 +728,7 @@ func downloadHTTP(ctx context.Context, localPath, lastModified, contentType, url
679
728
return err
680
729
}
681
730
682
- // If localPath was created by a parallel download keep it. Replacing it
683
- // while another process is copying it to the destination may fail the
684
- // clonefile syscall. We use a lock to ensure that only one process updates
685
- // data, and when we return data file exists.
686
-
687
- return lockutil .WithDirLock (filepath .Dir (localPath ), func () error {
688
- if _ , err := os .Stat (localPath ); err == nil {
689
- return nil
690
- } else if ! errors .Is (err , os .ErrNotExist ) {
691
- return err
692
- }
693
- return os .Rename (localPathTmp , localPath )
694
- })
731
+ return os .Rename (localPathTmp , localPath )
695
732
}
696
733
697
734
var tempfileCount atomic.Uint64
@@ -706,18 +743,6 @@ func perProcessTempfile(path string) string {
706
743
return fmt .Sprintf ("%s.tmp.%d.%d" , path , os .Getpid (), tempfileCount .Add (1 ))
707
744
}
708
745
709
- // writeFirst writes data to path unless path already exists.
710
- func writeFirst (path string , data []byte , perm os.FileMode ) error {
711
- return lockutil .WithDirLock (filepath .Dir (path ), func () error {
712
- if _ , err := os .Stat (path ); err == nil {
713
- return nil
714
- } else if ! errors .Is (err , os .ErrNotExist ) {
715
- return err
716
- }
717
- return os .WriteFile (path , data , perm )
718
- })
719
- }
720
-
721
746
// CacheEntries returns a map of cache entries.
722
747
// The key is the SHA256 of the URL.
723
748
// The value is the path to the cache entry.
0 commit comments