66 "errors"
77 "fmt"
88 "io"
9+ "io/fs"
910 "os"
1011 "path"
1112 "path/filepath"
@@ -296,11 +297,17 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st
296297 return wrapTerminal (fmt .Errorf ("catalog image is missing the required label %q" , ConfigDirLabel ), specIsCanonical )
297298 }
298299
299- if err := os .MkdirAll (unpackPath , 0700 ); err != nil {
300- return fmt .Errorf ("error creating unpack directory: %w" , err )
301- }
302300 l := log .FromContext (ctx )
303- l .Info ("unpacking image" , "path" , unpackPath )
301+ tempUnpackPath , err := os .MkdirTemp ("" , "unpack-*" )
302+ if err != nil {
303+ return fmt .Errorf ("error creating temporary unpack directory: %w" , err )
304+ }
305+ defer func () {
306+ if err := os .RemoveAll (tempUnpackPath ); err != nil {
307+ l .Error (err , "error removing temporary unpack directory" )
308+ }
309+ }()
310+ l .Info ("unpacking image" , "path" , unpackPath , "temp path" , tempUnpackPath )
304311 for i , layerInfo := range img .LayerInfos () {
305312 if err := func () error {
306313 layerReader , _ , err := layoutSrc .GetBlob (ctx , layerInfo , none .NoCache )
@@ -309,21 +316,66 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st
309316 }
310317 defer layerReader .Close ()
311318
312- if err := applyLayer (ctx , unpackPath , dirToUnpack , layerReader ); err != nil {
319+ if err := applyLayer (ctx , tempUnpackPath , dirToUnpack , layerReader ); err != nil {
313320 return fmt .Errorf ("error applying layer[%d]: %w" , i , err )
314321 }
315322 l .Info ("applied layer" , "layer" , i )
316323 return nil
317324 }(); err != nil {
318- return errors .Join (err , deleteRecursive (unpackPath ))
325+ return errors .Join (err , deleteRecursive (tempUnpackPath ))
319326 }
320327 }
328+
329+ // ensure unpack path is empty
330+ if err := os .RemoveAll (unpackPath ); err != nil {
331+ return fmt .Errorf ("error removing unpack path: %w" , err )
332+ }
333+ if err := copyRecursively (tempUnpackPath , unpackPath , 0700 ); err != nil {
334+ return fmt .Errorf ("error moving temporary unpack directory to final unpack directory: %w" , err )
335+ }
321336 if err := setReadOnlyRecursive (unpackPath ); err != nil {
322337 return fmt .Errorf ("error making unpack directory read-only: %w" , err )
323338 }
324339 return nil
325340}
326341
342+ func copyRecursively (srcPath string , destPath string , perm os.FileMode ) error {
343+ // ensure destination path not exist
344+ if stat , err := os .Stat (destPath ); stat != nil && ! os .IsNotExist (err ) {
345+ return fmt .Errorf ("destination path %q already exists: %w" , destPath , err )
346+ }
347+ return filepath .WalkDir (srcPath , func (path string , d fs.DirEntry , err error ) error {
348+ if err != nil {
349+ return err
350+ }
351+ relPath , err := filepath .Rel (srcPath , path )
352+ if err != nil {
353+ return err
354+ }
355+ if d .IsDir () {
356+ return os .MkdirAll (filepath .Join (destPath , relPath ), perm )
357+ }
358+ return copyFile (path , filepath .Join (destPath , relPath ))
359+ })
360+ }
361+
362+ func copyFile (src , dest string ) error {
363+ in , err := os .Open (src )
364+ if err != nil {
365+ return err
366+ }
367+ defer in .Close ()
368+
369+ out , err := os .Create (dest )
370+ if err != nil {
371+ return err
372+ }
373+ defer out .Close ()
374+
375+ _ , err = io .Copy (out , in )
376+ return err
377+ }
378+
327379func applyLayer (ctx context.Context , destPath string , srcPath string , layer io.ReadCloser ) error {
328380 decompressed , _ , err := compression .AutoDecompress (layer )
329381 if err != nil {
0 commit comments