6
6
"errors"
7
7
"fmt"
8
8
"io"
9
+ "io/fs"
9
10
"os"
10
11
"path"
11
12
"path/filepath"
@@ -80,8 +81,14 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, catalog *catalogdv
80
81
if ! unpackStat .IsDir () {
81
82
panic (fmt .Sprintf ("unexpected file at unpack path %q: expected a directory" , unpackPath ))
82
83
}
83
- l .Info ("image already unpacked" , "ref" , imgRef .String (), "digest" , canonicalRef .Digest ().String ())
84
- return successResult (unpackPath , canonicalRef , unpackStat .ModTime ()), nil
84
+
85
+ // check unpack directory is read-only
86
+ // this should only happen once all the contents have been unpacked and is a good
87
+ // indication that unpack completed successfully
88
+ if unpackStat .Mode ().Perm ()& 0200 == 0 {
89
+ l .Info ("image already unpacked" , "ref" , imgRef .String (), "digest" , canonicalRef .Digest ().String ())
90
+ return successResult (unpackPath , canonicalRef , unpackStat .ModTime ()), nil
91
+ }
85
92
}
86
93
87
94
//////////////////////////////////////////////////////
@@ -296,11 +303,17 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st
296
303
return wrapTerminal (fmt .Errorf ("catalog image is missing the required label %q" , ConfigDirLabel ), specIsCanonical )
297
304
}
298
305
299
- if err := os .MkdirAll (unpackPath , 0700 ); err != nil {
300
- return fmt .Errorf ("error creating unpack directory: %w" , err )
301
- }
302
306
l := log .FromContext (ctx )
303
- l .Info ("unpacking image" , "path" , unpackPath )
307
+ tempUnpackPath , err := os .MkdirTemp ("" , "unpack-*" )
308
+ if err != nil {
309
+ return fmt .Errorf ("error creating temporary unpack directory: %w" , err )
310
+ }
311
+ defer func () {
312
+ if err := os .RemoveAll (tempUnpackPath ); err != nil {
313
+ l .Error (err , "error removing temporary unpack directory" )
314
+ }
315
+ }()
316
+ l .Info ("unpacking image" , "path" , unpackPath , "temp path" , tempUnpackPath )
304
317
for i , layerInfo := range img .LayerInfos () {
305
318
if err := func () error {
306
319
layerReader , _ , err := layoutSrc .GetBlob (ctx , layerInfo , none .NoCache )
@@ -309,21 +322,76 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st
309
322
}
310
323
defer layerReader .Close ()
311
324
312
- if err := applyLayer (ctx , unpackPath , dirToUnpack , layerReader ); err != nil {
325
+ if err := applyLayer (ctx , tempUnpackPath , dirToUnpack , layerReader ); err != nil {
313
326
return fmt .Errorf ("error applying layer[%d]: %w" , i , err )
314
327
}
315
328
l .Info ("applied layer" , "layer" , i )
316
329
return nil
317
330
}(); err != nil {
318
- return errors .Join (err , deleteRecursive (unpackPath ))
331
+ return errors .Join (err , deleteRecursive (tempUnpackPath ))
319
332
}
320
333
}
334
+
335
+ // ensure unpack path is empty
336
+ if err := os .RemoveAll (unpackPath ); err != nil {
337
+ return fmt .Errorf ("error removing unpack path: %w" , err )
338
+ }
339
+ if err := copyRecursively (tempUnpackPath , unpackPath , 0700 ); err != nil {
340
+ return fmt .Errorf ("error moving temporary unpack directory to final unpack directory: %w" , err )
341
+ }
321
342
if err := setReadOnlyRecursive (unpackPath ); err != nil {
322
343
return fmt .Errorf ("error making unpack directory read-only: %w" , err )
323
344
}
324
345
return nil
325
346
}
326
347
348
+ func copyRecursively (srcPath string , destPath string , perm os.FileMode ) error {
349
+ // ensure destination path not exist
350
+ if _ , err := os .Stat (destPath ); err == nil {
351
+ return fmt .Errorf ("destination path %q already exists" , destPath )
352
+ } else if ! os .IsNotExist (err ) {
353
+ return fmt .Errorf ("error checking destination path: %w" , err )
354
+ }
355
+ return filepath .WalkDir (srcPath , func (path string , d fs.DirEntry , err error ) error {
356
+ if err != nil {
357
+ return err
358
+ }
359
+ relPath , err := filepath .Rel (srcPath , path )
360
+ if err != nil {
361
+ return err
362
+ }
363
+ destFullPath := filepath .Join (destPath , relPath )
364
+ if d .IsDir () {
365
+ return os .MkdirAll (destFullPath , perm )
366
+ }
367
+ if d .Type ()& os .ModeSymlink != 0 {
368
+ linkDest , err := os .Readlink (path )
369
+ if err != nil {
370
+ return err
371
+ }
372
+ return os .Symlink (linkDest , destFullPath )
373
+ }
374
+ return copyFile (path , destFullPath )
375
+ })
376
+ }
377
+
378
+ func copyFile (src , dest string ) error {
379
+ in , err := os .Open (src )
380
+ if err != nil {
381
+ return err
382
+ }
383
+ defer in .Close ()
384
+
385
+ out , err := os .Create (dest )
386
+ if err != nil {
387
+ return err
388
+ }
389
+ defer out .Close ()
390
+
391
+ _ , err = io .Copy (out , in )
392
+ return err
393
+ }
394
+
327
395
func applyLayer (ctx context.Context , destPath string , srcPath string , layer io.ReadCloser ) error {
328
396
decompressed , _ , err := compression .AutoDecompress (layer )
329
397
if err != nil {
0 commit comments