66 "errors"
77 "fmt"
88 "io"
9+ "io/fs"
910 "os"
1011 "path"
1112 "path/filepath"
@@ -80,8 +81,14 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, catalog *catalogdv
8081 if ! unpackStat .IsDir () {
8182 panic (fmt .Sprintf ("unexpected file at unpack path %q: expected a directory" , unpackPath ))
8283 }
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+ }
8592 }
8693
8794 //////////////////////////////////////////////////////
@@ -296,11 +303,17 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st
296303 return wrapTerminal (fmt .Errorf ("catalog image is missing the required label %q" , ConfigDirLabel ), specIsCanonical )
297304 }
298305
299- if err := os .MkdirAll (unpackPath , 0700 ); err != nil {
300- return fmt .Errorf ("error creating unpack directory: %w" , err )
301- }
302306 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 )
304317 for i , layerInfo := range img .LayerInfos () {
305318 if err := func () error {
306319 layerReader , _ , err := layoutSrc .GetBlob (ctx , layerInfo , none .NoCache )
@@ -309,21 +322,76 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st
309322 }
310323 defer layerReader .Close ()
311324
312- if err := applyLayer (ctx , unpackPath , dirToUnpack , layerReader ); err != nil {
325+ if err := applyLayer (ctx , tempUnpackPath , dirToUnpack , layerReader ); err != nil {
313326 return fmt .Errorf ("error applying layer[%d]: %w" , i , err )
314327 }
315328 l .Info ("applied layer" , "layer" , i )
316329 return nil
317330 }(); err != nil {
318- return errors .Join (err , deleteRecursive (unpackPath ))
331+ return errors .Join (err , deleteRecursive (tempUnpackPath ))
319332 }
320333 }
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+ }
321342 if err := setReadOnlyRecursive (unpackPath ); err != nil {
322343 return fmt .Errorf ("error making unpack directory read-only: %w" , err )
323344 }
324345 return nil
325346}
326347
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+
327395func applyLayer (ctx context.Context , destPath string , srcPath string , layer io.ReadCloser ) error {
328396 decompressed , _ , err := compression .AutoDecompress (layer )
329397 if err != nil {
0 commit comments